inspec 0.9.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 (247) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +65 -0
  4. data/.travis.yml +23 -0
  5. data/CHANGELOG.md +38 -0
  6. data/Gemfile +33 -0
  7. data/LICENSE +201 -0
  8. data/MAINTAINERS.md +28 -0
  9. data/MAINTAINERS.toml +42 -0
  10. data/README.md +257 -0
  11. data/Rakefile +47 -0
  12. data/bin/inspec +109 -0
  13. data/docs/ctl_inspec.rst +195 -0
  14. data/docs/dsl_inspec.rst +182 -0
  15. data/docs/readme.rst +100 -0
  16. data/docs/resources.rst +4319 -0
  17. data/docs/template.rst +51 -0
  18. data/examples/test-kitchen/.kitchen.yml +20 -0
  19. data/examples/test-kitchen/Berksfile +3 -0
  20. data/examples/test-kitchen/Gemfile +21 -0
  21. data/examples/test-kitchen/README.md +27 -0
  22. data/examples/test-kitchen/metadata.rb +7 -0
  23. data/examples/test-kitchen/recipes/default.rb +6 -0
  24. data/examples/test-kitchen/recipes/nginx.rb +30 -0
  25. data/examples/test-kitchen/test/integration/default/web_spec.rb +28 -0
  26. data/inspec.gemspec +30 -0
  27. data/lib/inspec.rb +20 -0
  28. data/lib/inspec/backend.rb +42 -0
  29. data/lib/inspec/dsl.rb +151 -0
  30. data/lib/inspec/log.rb +34 -0
  31. data/lib/inspec/metadata.rb +79 -0
  32. data/lib/inspec/plugins.rb +9 -0
  33. data/lib/inspec/plugins/resource.rb +62 -0
  34. data/lib/inspec/profile.rb +138 -0
  35. data/lib/inspec/profile_context.rb +170 -0
  36. data/lib/inspec/resource.rb +76 -0
  37. data/lib/inspec/rspec_json_formatter.rb +27 -0
  38. data/lib/inspec/rule.rb +170 -0
  39. data/lib/inspec/runner.rb +154 -0
  40. data/lib/inspec/shell.rb +66 -0
  41. data/lib/inspec/targets.rb +9 -0
  42. data/lib/inspec/targets/core.rb +27 -0
  43. data/lib/inspec/targets/dir.rb +67 -0
  44. data/lib/inspec/targets/file.rb +29 -0
  45. data/lib/inspec/targets/folder.rb +43 -0
  46. data/lib/inspec/targets/tar.rb +34 -0
  47. data/lib/inspec/targets/url.rb +39 -0
  48. data/lib/inspec/targets/zip.rb +47 -0
  49. data/lib/inspec/version.rb +7 -0
  50. data/lib/matchers/matchers.rb +221 -0
  51. data/lib/resources/apache.rb +29 -0
  52. data/lib/resources/apache_conf.rb +113 -0
  53. data/lib/resources/apt.rb +140 -0
  54. data/lib/resources/audit_policy.rb +63 -0
  55. data/lib/resources/auditd_conf.rb +56 -0
  56. data/lib/resources/auditd_rules.rb +53 -0
  57. data/lib/resources/bond.rb +65 -0
  58. data/lib/resources/bridge.rb +114 -0
  59. data/lib/resources/command.rb +57 -0
  60. data/lib/resources/csv.rb +32 -0
  61. data/lib/resources/directory.rb +15 -0
  62. data/lib/resources/etc_group.rb +150 -0
  63. data/lib/resources/file.rb +110 -0
  64. data/lib/resources/gem.rb +46 -0
  65. data/lib/resources/group.rb +132 -0
  66. data/lib/resources/host.rb +143 -0
  67. data/lib/resources/inetd_conf.rb +56 -0
  68. data/lib/resources/interface.rb +127 -0
  69. data/lib/resources/iptables.rb +65 -0
  70. data/lib/resources/json.rb +64 -0
  71. data/lib/resources/kernel_module.rb +40 -0
  72. data/lib/resources/kernel_parameter.rb +55 -0
  73. data/lib/resources/limits_conf.rb +55 -0
  74. data/lib/resources/login_def.rb +60 -0
  75. data/lib/resources/mysql.rb +81 -0
  76. data/lib/resources/mysql_conf.rb +116 -0
  77. data/lib/resources/mysql_session.rb +52 -0
  78. data/lib/resources/npm.rb +44 -0
  79. data/lib/resources/ntp_conf.rb +58 -0
  80. data/lib/resources/oneget.rb +63 -0
  81. data/lib/resources/os.rb +22 -0
  82. data/lib/resources/os_env.rb +34 -0
  83. data/lib/resources/package.rb +169 -0
  84. data/lib/resources/parse_config.rb +75 -0
  85. data/lib/resources/passwd.rb +93 -0
  86. data/lib/resources/pip.rb +75 -0
  87. data/lib/resources/port.rb +296 -0
  88. data/lib/resources/postgres.rb +37 -0
  89. data/lib/resources/postgres_conf.rb +87 -0
  90. data/lib/resources/postgres_session.rb +59 -0
  91. data/lib/resources/processes.rb +57 -0
  92. data/lib/resources/registry_key.rb +54 -0
  93. data/lib/resources/script.rb +34 -0
  94. data/lib/resources/security_policy.rb +73 -0
  95. data/lib/resources/service.rb +379 -0
  96. data/lib/resources/ssh_conf.rb +75 -0
  97. data/lib/resources/user.rb +374 -0
  98. data/lib/resources/windows_feature.rb +77 -0
  99. data/lib/resources/yaml.rb +23 -0
  100. data/lib/resources/yum.rb +154 -0
  101. data/lib/utils/convert.rb +12 -0
  102. data/lib/utils/detect.rb +15 -0
  103. data/lib/utils/find_files.rb +36 -0
  104. data/lib/utils/hash.rb +13 -0
  105. data/lib/utils/modulator.rb +12 -0
  106. data/lib/utils/parser.rb +61 -0
  107. data/lib/utils/simpleconfig.rb +115 -0
  108. data/tasks/maintainers.rb +213 -0
  109. data/test/docker_run.rb +156 -0
  110. data/test/docker_test.rb +51 -0
  111. data/test/helper.rb +200 -0
  112. data/test/integration/.kitchen.yml +42 -0
  113. data/test/integration/Berksfile +4 -0
  114. data/test/integration/cookbooks/os_prepare/metadata.rb +8 -0
  115. data/test/integration/cookbooks/os_prepare/recipes/apt.rb +20 -0
  116. data/test/integration/cookbooks/os_prepare/recipes/default.rb +9 -0
  117. data/test/integration/cookbooks/os_prepare/recipes/file.rb +21 -0
  118. data/test/integration/cookbooks/os_prepare/recipes/package.rb +26 -0
  119. data/test/integration/default/_debug_spec.rb +1 -0
  120. data/test/integration/default/apt_spec.rb +42 -0
  121. data/test/integration/default/file_spec.rb +109 -0
  122. data/test/integration/default/group_spec.rb +32 -0
  123. data/test/integration/default/kernel_module_spec.rb +17 -0
  124. data/test/integration/default/kernel_parameter_spec.rb +56 -0
  125. data/test/integration/default/package_spec.rb +11 -0
  126. data/test/integration/default/service_spec.rb +28 -0
  127. data/test/integration/default/user_spec.rb +44 -0
  128. data/test/resource/command_test.rb +33 -0
  129. data/test/resource/dsl_test.rb +45 -0
  130. data/test/resource/file_test.rb +130 -0
  131. data/test/resource/ssh_config.rb +9 -0
  132. data/test/resource/sshd_config.rb +9 -0
  133. data/test/test-extra.yaml +11 -0
  134. data/test/test.yaml +11 -0
  135. data/test/unit/mock/cmd/Get-NetAdapter +24 -0
  136. data/test/unit/mock/cmd/GetUserAccount +33 -0
  137. data/test/unit/mock/cmd/GetWin32Group +23 -0
  138. data/test/unit/mock/cmd/PATH +1 -0
  139. data/test/unit/mock/cmd/Resolve-DnsName +26 -0
  140. data/test/unit/mock/cmd/Test-NetConnection +4 -0
  141. data/test/unit/mock/cmd/auditctl +7 -0
  142. data/test/unit/mock/cmd/auditpol +2 -0
  143. data/test/unit/mock/cmd/brew-info-jq +1 -0
  144. data/test/unit/mock/cmd/chage-l-root +7 -0
  145. data/test/unit/mock/cmd/dpkg-s-curl +21 -0
  146. data/test/unit/mock/cmd/dscl +5 -0
  147. data/test/unit/mock/cmd/etc-apt +7 -0
  148. data/test/unit/mock/cmd/find-etc-rc-d-name-S +12 -0
  149. data/test/unit/mock/cmd/find-net-interface +9 -0
  150. data/test/unit/mock/cmd/gem-list-local-a-q-rubocop +1 -0
  151. data/test/unit/mock/cmd/get-net-tcpconnection +24 -0
  152. data/test/unit/mock/cmd/get-netadapter-binding-bridge +4 -0
  153. data/test/unit/mock/cmd/get-package-firefox +30 -0
  154. data/test/unit/mock/cmd/get-package-ruby +18 -0
  155. data/test/unit/mock/cmd/get-service-dhcp +10 -0
  156. data/test/unit/mock/cmd/get-windows-feature +7 -0
  157. data/test/unit/mock/cmd/getent-hosts-example.com +1 -0
  158. data/test/unit/mock/cmd/getent-passwd-root +1 -0
  159. data/test/unit/mock/cmd/id-chartmann +1 -0
  160. data/test/unit/mock/cmd/id-root +1 -0
  161. data/test/unit/mock/cmd/initctl-show-config-ssh +3 -0
  162. data/test/unit/mock/cmd/initctl-status-ssh +1 -0
  163. data/test/unit/mock/cmd/iptables-s +6 -0
  164. data/test/unit/mock/cmd/launchctl-list +3 -0
  165. data/test/unit/mock/cmd/ls-1-etc-init.d +2 -0
  166. data/test/unit/mock/cmd/ls-sys-class-net-br +2 -0
  167. data/test/unit/mock/cmd/lsmod +2 -0
  168. data/test/unit/mock/cmd/lsof-np-itcp +4 -0
  169. data/test/unit/mock/cmd/netstat-tulpen +5 -0
  170. data/test/unit/mock/cmd/npm-ls-g--json-bower +9 -0
  171. data/test/unit/mock/cmd/pacman-qi-curl +21 -0
  172. data/test/unit/mock/cmd/ping-example.com +6 -0
  173. data/test/unit/mock/cmd/pip-show-jinja2 +11 -0
  174. data/test/unit/mock/cmd/ps-aux +3 -0
  175. data/test/unit/mock/cmd/pw-usershow-root-7 +1 -0
  176. data/test/unit/mock/cmd/reg_schedule +1 -0
  177. data/test/unit/mock/cmd/rpm-qia-curl +24 -0
  178. data/test/unit/mock/cmd/sbin_sysctl +1 -0
  179. data/test/unit/mock/cmd/secedit-export +7 -0
  180. data/test/unit/mock/cmd/service-e +2 -0
  181. data/test/unit/mock/cmd/service-sendmail-onestatus +3 -0
  182. data/test/unit/mock/cmd/service-sshd-status +1 -0
  183. data/test/unit/mock/cmd/sockstat +5 -0
  184. data/test/unit/mock/cmd/success +0 -0
  185. data/test/unit/mock/cmd/systemctl-show-all-sshd +6 -0
  186. data/test/unit/mock/cmd/win32_product +8 -0
  187. data/test/unit/mock/cmd/yum-repolist-all +52 -0
  188. data/test/unit/mock/files/auditd.conf +4 -0
  189. data/test/unit/mock/files/bond0 +37 -0
  190. data/test/unit/mock/files/etcgroup +3 -0
  191. data/test/unit/mock/files/example.csv +6 -0
  192. data/test/unit/mock/files/inetd.conf +2 -0
  193. data/test/unit/mock/files/kitchen.yml +7 -0
  194. data/test/unit/mock/files/limits.conf +5 -0
  195. data/test/unit/mock/files/login.defs +5 -0
  196. data/test/unit/mock/files/mysql.conf +8 -0
  197. data/test/unit/mock/files/mysql2.conf +2 -0
  198. data/test/unit/mock/files/ntp.conf +5 -0
  199. data/test/unit/mock/files/passwd +2 -0
  200. data/test/unit/mock/files/policyfile.lock.json +12 -0
  201. data/test/unit/mock/files/ssh_config +5 -0
  202. data/test/unit/mock/files/sshd_config +7 -0
  203. data/test/unit/mock/profiles/empty/metadata.rb +0 -0
  204. data/test/unit/mock/profiles/metadata/metadata.rb +1 -0
  205. data/test/unit/profile_context_test.rb +140 -0
  206. data/test/unit/profile_test.rb +49 -0
  207. data/test/unit/resources/apt_test.rb +46 -0
  208. data/test/unit/resources/audit_policy_test.rb +13 -0
  209. data/test/unit/resources/auditd_conf_test.rb +15 -0
  210. data/test/unit/resources/auditd_rules_test.rb +21 -0
  211. data/test/unit/resources/bond_test.rb +24 -0
  212. data/test/unit/resources/bridge_test.rb +56 -0
  213. data/test/unit/resources/csv_test.rb +35 -0
  214. data/test/unit/resources/etc_group_test.rb +37 -0
  215. data/test/unit/resources/gem_test.rb +20 -0
  216. data/test/unit/resources/group_test.rb +96 -0
  217. data/test/unit/resources/host_test.rb +38 -0
  218. data/test/unit/resources/inetd_conf_test.rb +15 -0
  219. data/test/unit/resources/interface_test.rb +54 -0
  220. data/test/unit/resources/iptables_test.rb +30 -0
  221. data/test/unit/resources/json_test.rb +36 -0
  222. data/test/unit/resources/kernel_module_test.rb +23 -0
  223. data/test/unit/resources/kernel_parameter_test.rb +13 -0
  224. data/test/unit/resources/limits_conf_test.rb +14 -0
  225. data/test/unit/resources/login_def_test.rb +16 -0
  226. data/test/unit/resources/mysql_conf_test.rb +14 -0
  227. data/test/unit/resources/npm_test.rb +20 -0
  228. data/test/unit/resources/ntp_conf_test.rb +16 -0
  229. data/test/unit/resources/oneget_test.rb +45 -0
  230. data/test/unit/resources/os_env_test.rb +13 -0
  231. data/test/unit/resources/package_test.rb +51 -0
  232. data/test/unit/resources/passwd_test.rb +24 -0
  233. data/test/unit/resources/pip_test.rb +15 -0
  234. data/test/unit/resources/port_test.rb +46 -0
  235. data/test/unit/resources/processes_test.rb +32 -0
  236. data/test/unit/resources/registry_key_test.rb +19 -0
  237. data/test/unit/resources/script_test.rb +19 -0
  238. data/test/unit/resources/security_policy_test.rb +16 -0
  239. data/test/unit/resources/service_test.rb +116 -0
  240. data/test/unit/resources/ssh_conf_test.rb +33 -0
  241. data/test/unit/resources/user_test.rb +93 -0
  242. data/test/unit/resources/windows_feature.rb +17 -0
  243. data/test/unit/resources/yaml_test.rb +34 -0
  244. data/test/unit/resources/yum_test.rb +68 -0
  245. data/test/unit/simpleconfig_test.rb +80 -0
  246. data/test/unit/utils/content_parser_test.rb +30 -0
  247. metadata +555 -0
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+ # copyright: 2015, Vulcano Security GmbH
3
+ # author: Dominik Richter
4
+ # author: Christoph Hartmann
5
+ # license: All rights reserved
6
+
7
+ class MysqlSession < Inspec.resource(1)
8
+ name 'mysql_session'
9
+
10
+ def initialize(user = nil, pass = nil)
11
+ @user = user
12
+ @pass = pass
13
+ init_fallback if user.nil? or pass.nil?
14
+ skip_resource("Can't run MySQL SQL checks without authentication") if @user.nil? or @pass.nil?
15
+ end
16
+
17
+ def query(q, db = '')
18
+ # TODO: simple escape, must be handled by a library
19
+ # that does this securely
20
+ escaped_query = q.gsub(/\\/, '\\\\').gsub(/"/, '\\"').gsub(/\$/, '\\$')
21
+
22
+ # run the query
23
+ cmd = inspec.command("mysql -u#{@user} -p#{@pass} #{db} -s -e \"#{escaped_query}\"")
24
+ out = cmd.stdout + "\n" + cmd.stderr
25
+ if out =~ /Can't connect to .* MySQL server/ or
26
+ out.downcase =~ /^error/
27
+ # skip this test if the server can't run the query
28
+ skip_resource("Can't connect to MySQL instance for SQL checks.")
29
+ end
30
+
31
+ # return the raw command output
32
+ cmd
33
+ end
34
+
35
+ def to_s
36
+ 'MySQL Session'
37
+ end
38
+
39
+ private
40
+
41
+ def init_fallback
42
+ # support debian mysql administration login
43
+ debian = inspec.command('test -f /etc/mysql/debian.cnf && cat /etc/mysql/debian.cnf').stdout
44
+ return if debian.empty?
45
+
46
+ user = debian.match(/^\s*user\s*=\s*([^ ]*)\s*$/)
47
+ pass = debian.match(/^\s*password\s*=\s*([^ ]*)\s*$/)
48
+ return if user.nil? or pass.nil?
49
+ @user = user[1]
50
+ @pass = pass[1]
51
+ end
52
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+
5
+ # Usage:
6
+ # describe npm('bower') do
7
+ # it { should be_installed }
8
+ # end
9
+ class NpmPackage < Inspec.resource(1)
10
+ name 'npm'
11
+
12
+ def initialize(package_name)
13
+ @package_name = package_name
14
+ @cache = nil
15
+ end
16
+
17
+ def info
18
+ return @info if defined?(@info)
19
+
20
+ cmd = inspec.command("npm ls -g --json #{@package_name}")
21
+ @info = {
22
+ name: @package_name,
23
+ type: 'npm',
24
+ installed: cmd.exit_status == 0,
25
+ }
26
+ return @info unless @info[:installed]
27
+
28
+ pkgs = JSON.parse(cmd.stdout)
29
+ @info[:version] = pkgs['dependencies'][@package_name]['version']
30
+ @info
31
+ end
32
+
33
+ def installed?
34
+ info[:installed] == true
35
+ end
36
+
37
+ def version
38
+ info[:version]
39
+ end
40
+
41
+ def to_s
42
+ "Npm Package #{@package_name}"
43
+ end
44
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+ # copyright: 2015, Vulcano Security GmbH
3
+ # author: Christoph Hartmann
4
+ # author: Dominik Richter
5
+ # license: All rights reserved
6
+
7
+ require 'utils/simpleconfig'
8
+
9
+ # Usage:
10
+ #
11
+ # describe ntp_conf do
12
+ # its('server') { should_not eq nil }
13
+ # its('restrict') { should include '-4 default kod notrap nomodify nopeer noquery'}
14
+ # end
15
+
16
+ class NtpConf < Inspec.resource(1)
17
+ name 'ntp_conf'
18
+
19
+ def initialize(path = nil)
20
+ @conf_path = path || '/etc/ntp.conf'
21
+ end
22
+
23
+ def method_missing(name)
24
+ param = read_params[name.to_s]
25
+ # extract first value if we have only one value in array
26
+ return param[0] if param.is_a?(Array) and param.length == 1
27
+ param
28
+ end
29
+
30
+ def to_s
31
+ 'ntp.conf'
32
+ end
33
+
34
+ private
35
+
36
+ def read_params
37
+ return @params if defined?(@params)
38
+
39
+ if !inspec.file(@conf_path).file?
40
+ skip_resource "Can't find file \"#{@conf_path}\""
41
+ return @params = {}
42
+ end
43
+
44
+ content = inspec.file(@conf_path).content
45
+ if content.empty? && inspec.file(@conf_path).size > 0
46
+ skip_resource "Can't read file \"#{@conf_path}\""
47
+ return @params = {}
48
+ end
49
+
50
+ # parse the file
51
+ conf = SimpleConfig.new(
52
+ content,
53
+ assignment_re: /^\s*(\S+)\s+(.*)\s*$/,
54
+ multiple_values: true,
55
+ )
56
+ @params = conf.params
57
+ end
58
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+
5
+ # This resource talks with OneGet (https://github.com/OneGet/oneget)
6
+ # Its part of Windows Management Framework 5.0 and part of Windows 10
7
+ #
8
+ # Usage:
9
+ # describe oneget('zoomit') do
10
+ # it { should be_installed }
11
+ # end
12
+ class OneGetPackage < Inspec.resource(1)
13
+ name 'oneget'
14
+
15
+ def initialize(package_name)
16
+ @package_name = package_name
17
+
18
+ # verify that this resource is only supported on Windows
19
+ return skip_resource 'The `oneget` resource is not supported on your OS.' if inspec.os[:family] != 'windows'
20
+ end
21
+
22
+ def info
23
+ return @info if defined?(@info)
24
+
25
+ @info = {}
26
+ @info[:type] = 'oneget'
27
+ @info[:installed] = false
28
+
29
+ cmd = inspec.command("Get-Package -Name '#{@package_name}' | ConvertTo-Json")
30
+ # cannot rely on exit code for now, successful command returns exit code 1
31
+ # return nil if cmd.exit_status != 0
32
+ # try to parse json
33
+
34
+ begin
35
+ pkgs = JSON.parse(cmd.stdout)
36
+ @info[:installed] = true
37
+
38
+ # sometimes we get multiple values
39
+ if pkgs.is_a?(Array)
40
+ # select the first entry
41
+ pkgs = pkgs.first
42
+ end
43
+ rescue JSON::ParserError => _e
44
+ return @info
45
+ end
46
+
47
+ @info[:name] = pkgs['Name'] if pkgs.key?('Name')
48
+ @info[:version] = pkgs['Version'] if pkgs.key?('Version')
49
+ @info
50
+ end
51
+
52
+ def installed?
53
+ info[:installed] == true
54
+ end
55
+
56
+ def version
57
+ info[:version]
58
+ end
59
+
60
+ def to_s
61
+ "OneGet Package #{@package_name}"
62
+ end
63
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ class OS < Inspec.resource(1)
6
+ name 'os'
7
+
8
+ # reuse helper methods from backend
9
+ %w{redhat? debian? suse? bsd? solaris? linux? unix? windows?}.each do |os_family|
10
+ define_method((os_family).to_sym) do
11
+ inspec.backend.os.send(os_family)
12
+ end
13
+ end
14
+
15
+ def [](name)
16
+ inspec.backend.os[name]
17
+ end
18
+
19
+ def to_s
20
+ 'Operating System Detection'
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ # copyright: 2015, Vulcano Security GmbH
3
+ # author: Christoph Hartmann
4
+ # author: Dominik Richter
5
+ # license: All rights reserved
6
+
7
+ # Usage:
8
+ #
9
+ # describe os_env('PATH') do
10
+ # its(:split) { should_not include('') }
11
+ # its(:split) { should_not include('.') }
12
+ # end
13
+
14
+ class OsEnv < Inspec.resource(1)
15
+ name 'os_env'
16
+
17
+ attr_reader :content
18
+ def initialize(env)
19
+ @osenv = env
20
+ cmd = inspec.command("su - root -c 'echo $#{env}'")
21
+ @content = cmd.stdout.chomp
22
+ @content = nil if cmd.exit_status != 0
23
+ end
24
+
25
+ def split
26
+ # -1 is required to catch cases like dir1::dir2:
27
+ # where we have a trailing :
28
+ @content.nil? ? [] : @content.split(':', -1)
29
+ end
30
+
31
+ def to_s
32
+ "Environment Variable #{@osenv}"
33
+ end
34
+ end
@@ -0,0 +1,169 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+
5
+ # Resource to determine package information
6
+ #
7
+ # Usage:
8
+ # describe package('nginx') do
9
+ # it { should be_installed }
10
+ # end
11
+ class Package < Inspec.resource(1)
12
+ name 'package'
13
+
14
+ def initialize(package_name = nil)
15
+ @package_name = package_name
16
+ @name = @package_name
17
+ @cache = nil
18
+
19
+ # select package manager
20
+ @pkgman = nil
21
+ case inspec.os[:family]
22
+ when 'ubuntu', 'debian'
23
+ @pkgman = Deb.new(inspec)
24
+ when 'redhat', 'fedora', 'centos', 'opensuse'
25
+ @pkgman = Rpm.new(inspec)
26
+ when 'arch'
27
+ @pkgman = Pacman.new(inspec)
28
+ when 'darwin'
29
+ @pkgman = Brew.new(inspec)
30
+ when 'windows'
31
+ @pkgman = WindowsPkg.new(inspec)
32
+ else
33
+ return skip_resource 'The `package` resource is not supported on your OS yet.'
34
+ end
35
+ end
36
+
37
+ # returns true if the package is installed
38
+ def installed?(_provider = nil, _version = nil)
39
+ return false if info.nil?
40
+ info[:installed] == true
41
+ end
42
+
43
+ # returns the package description
44
+ def info
45
+ return @cache if !@cache.nil?
46
+ return nil if @pkgman.nil?
47
+ @pkgman.info(@package_name)
48
+ end
49
+
50
+ # return the package version
51
+ def version
52
+ info = @pkgman.info(@package_name)
53
+ return nil if info.nil?
54
+ info[:version]
55
+ end
56
+
57
+ def to_s
58
+ "System Package #{@package_name}"
59
+ end
60
+ end
61
+
62
+ class PkgManagement
63
+ attr_reader :inspec
64
+ def initialize(inspec)
65
+ @inspec = inspec
66
+ end
67
+ end
68
+
69
+ # Debian / Ubuntu
70
+ class Deb < PkgManagement
71
+ def info(package_name)
72
+ cmd = inspec.command("dpkg -s #{package_name}")
73
+ return nil if cmd.exit_status.to_i != 0
74
+
75
+ params = SimpleConfig.new(
76
+ cmd.stdout.chomp,
77
+ assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
78
+ multiple_values: false,
79
+ ).params
80
+ {
81
+ name: params['Package'],
82
+ installed: true,
83
+ version: params['Version'],
84
+ type: 'deb',
85
+ }
86
+ end
87
+ end
88
+
89
+ # RHEL family
90
+ class Rpm < PkgManagement
91
+ def info(package_name)
92
+ cmd = inspec.command("rpm -qia #{package_name}")
93
+ # CentOS does not return an error code if the package is not installed,
94
+ # therefore we need to check for emptyness
95
+ return nil if cmd.exit_status.to_i != 0 || cmd.stdout.chomp.empty?
96
+ params = SimpleConfig.new(
97
+ cmd.stdout.chomp,
98
+ assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
99
+ multiple_values: false,
100
+ ).params
101
+ {
102
+ name: params['Name'],
103
+ installed: true,
104
+ version: params['Version'],
105
+ type: 'rpm',
106
+ }
107
+ end
108
+ end
109
+
110
+ # MacOS / Darwin implementation
111
+ class Brew < PkgManagement
112
+ def info(package_name)
113
+ cmd = inspec.command("brew info --json=v1 #{package_name}")
114
+ return nil if cmd.exit_status.to_i != 0
115
+ # parse data
116
+ pkg = JSON.parse(cmd.stdout)[0]
117
+ {
118
+ name: "#{pkg.name}",
119
+ installed: true,
120
+ version: "#{pkg.installed.version}",
121
+ type: 'brew',
122
+ }
123
+ end
124
+ end
125
+
126
+ # Arch Linux
127
+ class Pacman < PkgManagement
128
+ def info(package_name)
129
+ cmd = inspec.command("pacman -Qi #{package_name}")
130
+ return nil if cmd.exit_status.to_i != 0
131
+
132
+ params = SimpleConfig.new(
133
+ cmd.stdout.chomp,
134
+ assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
135
+ multiple_values: false,
136
+ ).params
137
+
138
+ {
139
+ name: params['Name'],
140
+ installed: true,
141
+ version: params['Version'],
142
+ type: 'pacman',
143
+ }
144
+ end
145
+ end
146
+
147
+ # Determines the installed packages on Windows
148
+ # Currently we use 'Get-WmiObject -Class Win32_Product' as a detection method
149
+ # TODO: evaluate if alternative methods as proposed by Microsoft are still valid:
150
+ # @see: http://blogs.technet.com/b/heyscriptingguy/archive/2013/11/15/use-powershell-to-find-installed-software.aspx
151
+ class WindowsPkg < PkgManagement
152
+ def info(package_name)
153
+ # Find the package
154
+ cmd = inspec.command("Get-WmiObject -Class Win32_Product | Where-Object {$_.Name -eq '#{package_name}'} | Select-Object -Property Name,Version,Vendor,PackageCode,Caption,Description | ConvertTo-Json")
155
+
156
+ begin
157
+ package = JSON.parse(cmd.stdout)
158
+ rescue JSON::ParserError => _e
159
+ return nil
160
+ end
161
+
162
+ {
163
+ name: package['Name'],
164
+ installed: true,
165
+ version: package['Version'],
166
+ type: 'windows',
167
+ }
168
+ end
169
+ end