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,37 @@
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 Postgres < Inspec.resource(1)
8
+ name 'postgres'
9
+
10
+ attr_reader :service, :data_dir, :conf_dir, :conf_path
11
+ def initialize
12
+ case inspec.os[:family]
13
+ when 'ubuntu', 'debian'
14
+ @service = 'postgresql'
15
+ @data_dir = '/var/lib/postgresql'
16
+ @version = inspec.command('ls /etc/postgresql/').stdout.chomp
17
+ @conf_dir = "/etc/postgresql/#{@version}/main"
18
+ @conf_path = File.join @conf_dir, 'postgresql.conf'
19
+
20
+ when 'arch'
21
+ @service = 'postgresql'
22
+ @data_dir = '/var/lib/postgres/data'
23
+ @conf_dir = '/var/lib/postgres/data'
24
+ @conf_path = File.join @conf_dir, 'postgresql.conf'
25
+
26
+ else
27
+ @service = 'postgresql'
28
+ @data_dir = '/var/lib/postgresql'
29
+ @conf_dir = '/var/lib/pgsql/data'
30
+ @conf_path = File.join @conf_dir, 'postgresql.conf'
31
+ end
32
+ end
33
+
34
+ def to_s
35
+ 'PostgreSQL'
36
+ end
37
+ end
@@ -0,0 +1,87 @@
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
+ require 'utils/simpleconfig'
8
+ require 'utils/find_files'
9
+ require 'resources/postgres'
10
+
11
+ class PostgresConf < Inspec.resource(1)
12
+ name 'postgres_conf'
13
+
14
+ include FindFiles
15
+
16
+ def initialize(conf_path = nil)
17
+ @conf_path = conf_path || inspec.postgres.conf_path
18
+ @conf_dir = File.expand_path(File.dirname @conf_path)
19
+ @files_contents = {}
20
+ @content = nil
21
+ @params = nil
22
+ read_content
23
+ end
24
+
25
+ def content
26
+ @content ||= read_content
27
+ end
28
+
29
+ def params(*opts)
30
+ @params || read_content
31
+ res = @params
32
+ opts.each do |opt|
33
+ res = res[opt] unless res.nil?
34
+ end
35
+ res
36
+ end
37
+
38
+ def read_content
39
+ @content = ''
40
+ @params = {}
41
+
42
+ # skip if the main configuration file doesn't exist
43
+ if !inspec.file(@conf_path).file?
44
+ return skip_resource "Can't find file \"#{@conf_path}\""
45
+ end
46
+ raw_conf = read_file(@conf_path)
47
+ if raw_conf.empty? && inspec.file(@conf_path).size > 0
48
+ return skip_resource("Can't read file \"#{@conf_path}\"")
49
+ end
50
+
51
+ to_read = [@conf_path]
52
+ until to_read.empty?
53
+ raw_conf = read_file(to_read[0])
54
+ @content += raw_conf
55
+
56
+ params = SimpleConfig.new(raw_conf).params
57
+ @params.merge!(params)
58
+
59
+ to_read = to_read.drop(1)
60
+ # see if there is more config files to include
61
+
62
+ to_read += include_files(params).find_all do |fp|
63
+ not @files_contents.key? fp
64
+ end
65
+ end
66
+ @content
67
+ end
68
+
69
+ def include_files(params)
70
+ include_files = params['include'] || []
71
+ include_files += params['include_if_exists'] || []
72
+ dirs = params['include_dir'] || []
73
+ dirs.each do |dir|
74
+ dir = File.join(@conf_dir, dir) if dir[0] != '/'
75
+ include_files += find_files(dir, depth: 1, type: 'file')
76
+ end
77
+ include_files
78
+ end
79
+
80
+ def read_file(path)
81
+ @files_contents[path] ||= inspec.file(path).content
82
+ end
83
+
84
+ def to_s
85
+ 'PostgreSQL Configuration'
86
+ end
87
+ end
@@ -0,0 +1,59 @@
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 Lines
8
+ def initialize(raw, desc)
9
+ @raw = raw
10
+ @desc = desc
11
+ end
12
+
13
+ def output
14
+ @raw
15
+ end
16
+
17
+ def lines
18
+ @raw.split("\n")
19
+ end
20
+
21
+ def to_s
22
+ @desc
23
+ end
24
+ end
25
+
26
+ class PostgresSession < Inspec.resource(1)
27
+ name 'postgres_session'
28
+
29
+ def initialize(user, pass)
30
+ @user = user || 'postgres'
31
+ @pass = pass
32
+ end
33
+
34
+ def query(query, db = [], &block)
35
+ dbs = db.map { |x| "-d #{x}" }.join(' ')
36
+ # TODO: simple escape, must be handled by a library
37
+ # that does this securely
38
+ escaped_query = query.gsub(/\\/, '\\\\').gsub(/"/, '\\"').gsub(/\$/, '\\$')
39
+ # run the query
40
+ cmd = inspec.command("PGPASSWORD='#{@pass}' psql -U #{@user} #{dbs} -c \"#{escaped_query}\"")
41
+ out = cmd.stdout + "\n" + cmd.stderr
42
+ if out =~ /could not connect to .*/ or
43
+ out.downcase =~ /^error/
44
+ # skip this test if the server can't run the query
45
+ RSpec.describe(cmd) do
46
+ it 'is skipped', skip: out do
47
+ end
48
+ end
49
+ else
50
+ # remove the whole header (i.e. up to the first ^-----+------+------$)
51
+ # remove the tail
52
+ lines = cmd.stdout
53
+ .sub(/(.*\n)+([-]+[+])*[-]+\n/, '')
54
+ .sub(/\n[^\n]*\n\n$/, '')
55
+ l = Lines.new(lines.strip, "PostgreSQL query: #{query}")
56
+ RSpec.__send__('describe', l, &block)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,57 @@
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 Processes < Inspec.resource(1)
8
+ name 'processes'
9
+
10
+ attr_reader :list
11
+ def initialize(grep)
12
+ # turn into a regexp if it isn't one yet
13
+ if grep.class == String
14
+ grep = '(/[^/]*)*'+grep if grep[0] != '/'
15
+ grep = Regexp.new('^' + grep + '(\s|$)')
16
+ end
17
+
18
+ all_cmds = ps_aux
19
+ @list = all_cmds.find_all do |hm|
20
+ hm[:command] =~ grep
21
+ end
22
+ end
23
+
24
+ def to_s
25
+ 'Processes'
26
+ end
27
+
28
+ private
29
+
30
+ def ps_aux
31
+ # get all running processes
32
+ cmd = inspec.command('ps aux')
33
+ all = cmd.stdout.split("\n")[1..-1]
34
+ return [] if all.nil?
35
+
36
+ lines = all.map do |line|
37
+ # user 32296 0.0 0.0 42592 7972 pts/15 Ss+ Apr06 0:00 zsh
38
+ line.match(/^([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(.*)$/)
39
+ end.compact
40
+
41
+ lines.map do |m|
42
+ {
43
+ user: m[1],
44
+ pid: m[2],
45
+ cpu: m[3],
46
+ mem: m[4],
47
+ vsz: m[5],
48
+ rss: m[6],
49
+ tty: m[7],
50
+ stat: m[8],
51
+ start: m[9],
52
+ time: m[10],
53
+ command: m[11],
54
+ }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+ # copyright: 2015, Vulcano Security GmbH
3
+ # author: Christoph Hartmann
4
+ # license: All rights reserved
5
+
6
+ require 'json'
7
+
8
+ # Usage:
9
+ # describe registry_key('Task Scheduler','HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Schedule') do
10
+ # its('Start') { should eq 2 }
11
+ # end
12
+
13
+ class RegistryKey < Inspec.resource(1)
14
+ name 'registry_key'
15
+
16
+ attr_accessor :reg_key
17
+
18
+ def initialize(name, reg_key = nil)
19
+ # if we have one parameter, we use it as name
20
+ reg_key ||= name
21
+ @name = name
22
+ @reg_key = reg_key
23
+ end
24
+
25
+ def registry_value(path, key)
26
+ cmd = "(Get-Item 'Registry::#{path}').GetValue('#{key}')"
27
+ command_result ||= inspec.command(cmd)
28
+ val = { exit_code: command_result.exit_status.to_i, data: command_result.stdout }
29
+ val
30
+ end
31
+
32
+ def convert_value(value)
33
+ val = value.strip
34
+ val = val.to_i if val.match(/^\d+$/)
35
+ val
36
+ end
37
+
38
+ # returns nil, if not existant or value
39
+ def method_missing(meth)
40
+ # get data
41
+ val = registry_value(@reg_key, meth)
42
+
43
+ # verify data
44
+ if (val[:exit_code] == 0)
45
+ return convert_value(val[:data])
46
+ else
47
+ return nil
48
+ end
49
+ end
50
+
51
+ def to_s
52
+ "Registry Key #{@name}"
53
+ end
54
+ 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
+ class Script < Cmd
8
+ name 'script'
9
+ attr_accessor :command
10
+
11
+ def initialize(script)
12
+ case inspec.os[:family]
13
+ when 'windows'
14
+ # encodes a script as base64 to run as powershell encodedCommand
15
+ # this comes with performance issues: @see https://gist.github.com/fnichol/7b20596b950e65fb96f9
16
+ require 'winrm'
17
+ script = WinRM::PowershellScript.new(script)
18
+ cmd = "powershell -encodedCommand #{script.encoded}"
19
+ else
20
+ return skip_resource 'The `script` resource is not supported on your OS yet.'
21
+ end
22
+
23
+ @command = cmd
24
+ end
25
+
26
+ # we cannot determine if a command exists, because that does not work for scripts
27
+ def exist?
28
+ nil
29
+ end
30
+
31
+ def to_s
32
+ 'Script'
33
+ end
34
+ end
@@ -0,0 +1,73 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+ #
5
+ # Security Configuration and Analysis
6
+ #
7
+ # Export local security policy:
8
+ # secedit /export /cfg secpol.cfg
9
+ #
10
+ # @link http://www.microsoft.com/en-us/download/details.aspx?id=25250
11
+ #
12
+ # In Windows, some security options are managed differently that the local GPO
13
+ # All local GPO parameters can be examined via Registry, but not all security
14
+ # parameters. Therefore we need a combination of Registry and secedit output
15
+
16
+ class SecurityPolicy < Inspec.resource(1)
17
+ name 'security_policy'
18
+
19
+ def initialize
20
+ @loaded = false
21
+ @policy = nil
22
+ @exit_status = nil
23
+ end
24
+
25
+ # load security content
26
+ def load
27
+ # export the security policy
28
+ inspec.command('secedit /export /cfg win_secpol.cfg')
29
+ # store file content
30
+ command_result ||= inspec.command('type win_secpol.cfg')
31
+ # delete temp file
32
+ inspec.command('del win_secpol.cfg')
33
+
34
+ @exit_status = command_result.exit_status.to_i
35
+ @policy = command_result.stdout
36
+ @loaded = true
37
+
38
+ # returns self
39
+ self
40
+ end
41
+
42
+ def method_missing(method)
43
+ # load data if needed
44
+ if (@loaded == false)
45
+ load
46
+ end
47
+
48
+ # find line with key
49
+ key = Regexp.escape(method.to_s)
50
+ target = ''
51
+ @policy.each_line {|s|
52
+ target = s.strip if s.match(/^\s*#{key}\s*=\s*(.*)\b/)
53
+ }
54
+
55
+ # extract variable value
56
+ result = target.match(/[=]{1}\s*(?<value>.*)/)
57
+
58
+ if !result.nil?
59
+ val = result[:value]
60
+ val = val.to_i if val.match(/^\d+$/)
61
+ else
62
+ # TODO: we may need to return skip or failure if the
63
+ # requested value is not available
64
+ val = nil
65
+ end
66
+
67
+ val
68
+ end
69
+
70
+ def to_s
71
+ 'Security Policy'
72
+ end
73
+ end
@@ -0,0 +1,379 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+ # license: All rights reserved
5
+
6
+ # Usage:
7
+ # describe service('dhcp') do
8
+ # it { should be_enabled }
9
+ # it { should be_installed }
10
+ # it { should be_running }
11
+ # end
12
+ #
13
+ # We detect the init system for each operating system, based on the operating
14
+ # system.
15
+ #
16
+ # Fedora 15 : systemd
17
+ # RedHat 7 : systemd
18
+ # Ubuntu 15.04 : systemd
19
+ # Ubuntu < 15.04 : upstart
20
+ #
21
+ # TODO: extend the logic to detect the running init system, independently of OS
22
+ class Service < Inspec.resource(1)
23
+ name 'service'
24
+
25
+ def initialize(service_name)
26
+ @service_name = service_name
27
+ @service_mgmt = nil
28
+ @cache = nil
29
+ select_package_manager
30
+ end
31
+
32
+ def select_package_manager # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
33
+ family = inspec.os[:family]
34
+
35
+ case family
36
+ # Ubuntu
37
+ # @see: https://wiki.ubuntu.com/SystemdForUpstartUsers
38
+ # Ubuntu 15.04 : Systemd
39
+ # Systemd runs with PID 1 as /sbin/init.
40
+ # Upstart runs with PID 1 as /sbin/upstart.
41
+ # Ubuntu < 15.04 : Upstart
42
+ # Upstart runs with PID 1 as /sbin/init.
43
+ # Systemd runs with PID 1 as /lib/systemd/systemd.
44
+ when 'ubuntu'
45
+ version = inspec.os[:release].to_f
46
+ if version < 15.04
47
+ @service_mgmt = Upstart.new(inspec)
48
+ else
49
+ @service_mgmt = Systemd.new(inspec)
50
+ end
51
+ when 'debian'
52
+ version = inspec.os[:release].to_i
53
+ if version > 7
54
+ @service_mgmt = Systemd.new(inspec)
55
+ else
56
+ @service_mgmt = SysV.new(inspec)
57
+ end
58
+ when 'redhat', 'fedora', 'centos'
59
+ version = inspec.os[:release].to_i
60
+ if (%w{ redhat centos }.include?(family) && version >= 7) || (family == 'fedora' && version >= 15)
61
+ @service_mgmt = Systemd.new(inspec)
62
+ else
63
+ @service_mgmt = SysV.new(inspec)
64
+ end
65
+ when 'darwin'
66
+ @service_mgmt = LaunchCtl.new(inspec)
67
+ when 'windows'
68
+ @service_mgmt = WindowsSrv.new(inspec)
69
+ when 'freebsd'
70
+ @service_mgmt = BSDInit.new(inspec)
71
+ when 'arch', 'opensuse'
72
+ @service_mgmt = Systemd.new(inspec)
73
+ end
74
+
75
+ return skip_resource 'The `service` resource is not supported on your OS yet.' if @service_mgmt.nil?
76
+ end
77
+
78
+ def info
79
+ return @cache if !@cache.nil?
80
+ return nil if @service_mgmt.nil?
81
+ @cache = @service_mgmt.info(@service_name)
82
+ end
83
+
84
+ # verifies the service is enabled
85
+ def enabled?(_level = nil)
86
+ return false if info.nil?
87
+ info[:enabled]
88
+ end
89
+
90
+ # verifies the service is registered
91
+ def installed?(_name = nil, _version = nil)
92
+ return false if info.nil?
93
+ info[:installed]
94
+ end
95
+
96
+ # verifies the service is currently running
97
+ def running?(_under = nil)
98
+ return false if info.nil?
99
+ info[:running]
100
+ end
101
+
102
+ def to_s
103
+ "Service #{@service_name}"
104
+ end
105
+ end
106
+
107
+ class ServiceManager
108
+ attr_reader :inspec
109
+ def initialize(inspec)
110
+ @inspec = inspec
111
+ end
112
+ end
113
+
114
+ # @see: http://www.freedesktop.org/software/systemd/man/systemctl.html
115
+ # @see: http://www.freedesktop.org/software/systemd/man/systemd-system.conf.html
116
+ class Systemd < ServiceManager
117
+ def info(service_name)
118
+ cmd = inspec.command("systemctl show --all #{service_name}")
119
+ return nil if cmd.exit_status.to_i != 0
120
+
121
+ # parse data
122
+ params = SimpleConfig.new(
123
+ cmd.stdout.chomp,
124
+ assignment_re: /^\s*([^=]*?)\s*=\s*(.*?)\s*$/,
125
+ multiple_values: false,
126
+ ).params
127
+
128
+ # LoadState values eg. loaded, not-found
129
+ params['LoadState'] == 'loaded' ? (installed = true) : (installed = false)
130
+ # test via 'systemctl is-active service'
131
+ # SubState values running
132
+ params['SubState'] == 'running' ? (running = true) : (running = false)
133
+ # test via systemctl --quiet is-enabled
134
+ # ActiveState values eg.g inactive, active
135
+ params['ActiveState'] == 'active' ? (enabled = true) : (enabled = false)
136
+
137
+ {
138
+ name: params['Id'],
139
+ description: params['Description'],
140
+ installed: installed,
141
+ running: running,
142
+ enabled: enabled,
143
+ type: 'systemd',
144
+ }
145
+ end
146
+ end
147
+
148
+ # @see: http://upstart.ubuntu.com
149
+ class Upstart < ServiceManager
150
+ def info(service_name)
151
+ # get the status of upstart service
152
+ cmd = inspec.command("initctl status #{service_name}")
153
+ return nil if cmd.exit_status != 0
154
+
155
+ # @see: http://upstart.ubuntu.com/cookbook/#job-states
156
+ # grep for running to indicate the service is there
157
+ match_running = /running/.match(cmd.stdout)
158
+ !match_running.nil? ? (running = true) : (running = false)
159
+
160
+ # check if a service is enabled
161
+ # http://upstart.ubuntu.com/cookbook/#determine-if-a-job-is-disabled
162
+ # $ initctl show-config $job | grep -q "^ start on" && echo enabled || echo disabled
163
+ # Ubuntu 10.04 show-config is not supported
164
+ # @see http://manpages.ubuntu.com/manpages/maverick/man8/initctl.8.html
165
+ config = inspec.command("initctl show-config #{service_name}")
166
+ match_enabled = /^\s*start on/.match(config.stdout)
167
+ !match_enabled.nil? ? (enabled = true) : (enabled = false)
168
+
169
+ # implement fallback for Ubuntu 10.04
170
+ if inspec.os[:family] == 'ubuntu' &&
171
+ inspec.os[:release].to_f >= 10.04 &&
172
+ inspec.os[:release].to_f < 12.04 &&
173
+ cmd.exit_status == 0
174
+ enabled = true
175
+ end
176
+
177
+ {
178
+ name: service_name,
179
+ description: nil,
180
+ installed: true,
181
+ running: running,
182
+ enabled: enabled,
183
+ type: 'upstart',
184
+ }
185
+ end
186
+ end
187
+
188
+ class SysV < ServiceManager
189
+ def info(service_name)
190
+ # check if service is installed
191
+ # read all available services via ls /etc/init.d/
192
+ srvlist = inspec.command('ls -1 /etc/init.d/')
193
+ return nil if srvlist.exit_status != 0
194
+
195
+ # check if the service is in list
196
+ service = srvlist.stdout.split("\n").select { |srv| srv == service_name }
197
+
198
+ # abort if we could not find any service
199
+ return nil if service.empty?
200
+
201
+ # read all enabled services from runlevel
202
+ # on rhel via: 'chkconfig --list', is not installed by default
203
+ # bash: for i in `find /etc/rc*.d -name S*`; do basename $i | sed -r 's/^S[0-9]+//'; done | sort | uniq
204
+ enabled_services_cmd = inspec.command('find /etc/rc*.d -name S*')
205
+ enabled_services = enabled_services_cmd.stdout.split("\n").select { |line|
206
+ /(^.*#{service_name}.*)/.match(line)
207
+ }
208
+ enabled_services.empty? ? enabled = false : enabled = true
209
+
210
+ # check if service is really running
211
+ # service throws an exit code if the service is not installed or
212
+ # not enabled
213
+
214
+ # on debian service is located /usr/sbin/service, on centos it is located here /sbin/service
215
+ service_cmd = 'service'
216
+ service_cmd = '/usr/sbin/service' if inspec.os[:family] == 'debian'
217
+ service_cmd = '/sbin/service' if inspec.os[:family] == 'centos'
218
+
219
+ cmd = inspec.command("#{service_cmd} #{service_name} status")
220
+ cmd.exit_status == 0 ? (running = true) : (running = false)
221
+ {
222
+ name: service_name,
223
+ description: nil,
224
+ installed: true,
225
+ running: running,
226
+ enabled: enabled,
227
+ type: 'sysv',
228
+ }
229
+ end
230
+ end
231
+
232
+ # @see: https://www.freebsd.org/doc/en/articles/linux-users/startup.html
233
+ # @see: https://www.freebsd.org/cgi/man.cgi?query=rc.conf&sektion=5
234
+ class BSDInit < ServiceManager
235
+ def info(service_name)
236
+ # check if service is enabled
237
+ # services are enabled in /etc/rc.conf and /etc/defaults/rc.conf
238
+ # via #{service_name}_enable="YES"
239
+ # service SERVICE status returns the following result if not activated:
240
+ # Cannot 'status' sshd. Set sshd_enable to YES in /etc/rc.conf or use 'onestatus' instead of 'status'.
241
+ # gather all enabled services
242
+ cmd = inspec.command('service -e')
243
+ return nil if cmd.exit_status != 0
244
+
245
+ # search for the service
246
+ srv = /(^.*#{service_name}$)/.match(cmd.stdout)
247
+ return nil if srv.nil? || srv[0].nil?
248
+ enabled = true
249
+
250
+ # check if the service is running
251
+ # if the service is not available or not running, we always get an error code
252
+ cmd = inspec.command("service #{service_name} onestatus")
253
+ cmd.exit_status == 0 ? (running = true) : (running = false)
254
+
255
+ {
256
+ name: service_name,
257
+ description: nil,
258
+ installed: true,
259
+ running: running,
260
+ enabled: enabled,
261
+ type: 'bsd-init',
262
+ }
263
+ end
264
+ end
265
+
266
+ # MacOS / Darwin
267
+ # new launctl on macos 10.10
268
+ class LaunchCtl < ServiceManager
269
+ def info(service_name)
270
+ # get the status of upstart service
271
+ cmd = inspec.command('launchctl list')
272
+ return nil if cmd.exit_status != 0
273
+
274
+ # search for the service
275
+ srv = /(^.*#{service_name}.*)/.match(cmd.stdout)
276
+ return nil if srv.nil? || srv[0].nil?
277
+
278
+ # extract values from service
279
+ parsed_srv = /^([0-9]+)\s*(\w*)\s*(\S*)/.match(srv[0])
280
+ !parsed_srv.nil? ? (enabled = true) : (enabled = false)
281
+
282
+ # check if the service is running
283
+ pid = parsed_srv[0]
284
+ !pid.nil? ? (running = true) : (running = false)
285
+
286
+ # extract service label
287
+ srv = parsed_srv[3] || service_name
288
+
289
+ {
290
+ name: srv,
291
+ description: nil,
292
+ installed: true,
293
+ running: running,
294
+ enabled: enabled,
295
+ type: 'darwin',
296
+ }
297
+ end
298
+ end
299
+
300
+ # Determine the service state from Windows
301
+ # Uses Powershell to retrieve the information
302
+ class WindowsSrv < ServiceManager
303
+ # Determine service details
304
+ # PS: Get-Service -Name 'dhcp'| Select-Object -Property Name, DisplayName, Status | ConvertTo-Json
305
+ # {
306
+ # "Name": "dhcp",
307
+ # "DisplayName": "DHCP Client",
308
+ # "Status": 4
309
+ # }
310
+ #
311
+ # Until StartMode is not added to Get-Service, we need to do a workaround
312
+ # @see: https://connect.microsoft.com/PowerShell/feedback/details/424948/i-would-like-to-see-the-property-starttype-added-to-get-services
313
+ # Use the following powershell to determine the start mode
314
+ # PS: Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $name -or $_.DisplayName -eq $name} | Select-Object -Prop
315
+ # erty Name, StartMode, State, Status | ConvertTo-Json
316
+ # {
317
+ # "Name": "Dhcp",
318
+ # "StartMode": "Auto",
319
+ # "State": "Running",
320
+ # "Status": "OK"
321
+ # }
322
+ #
323
+ # Windows Services have the following status code:
324
+ # @see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms685996(v=vs.85).aspx
325
+ # - 1: Stopped
326
+ # - 2: Starting
327
+ # - 3: Stopping
328
+ # - 4: Running
329
+ # - 5: Continue Pending
330
+ # - 6: Pause Pending
331
+ # - 7: Paused
332
+ def info(service_name)
333
+ cmd = inspec.command("New-Object -Type PSObject | Add-Member -MemberType NoteProperty -Name Service -Value (Get-Service -Name #{service_name}| Select-Object -Property Name, DisplayName, Status) -PassThru | Add-Member -MemberType NoteProperty -Name WMI -Value (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq '#{service_name}' -or $_.DisplayName -eq '#{service_name}'} | Select-Object -Property StartMode) -PassThru | ConvertTo-Json")
334
+
335
+ # cannot rely on exit code for now, successful command returns exit code 1
336
+ # return nil if cmd.exit_status != 0
337
+ # try to parse json
338
+ begin
339
+ service = JSON.parse(cmd.stdout)
340
+ rescue JSON::ParserError => _e
341
+ return nil
342
+ end
343
+
344
+ # check that we got a response
345
+ return nil if service.nil? || service['Service'].nil?
346
+
347
+ {
348
+ name: service['Service']['Name'],
349
+ description: service['Service']['DisplayName'],
350
+ installed: true,
351
+ running: service_running?(service),
352
+ enabled: service_enabled?(service),
353
+ type: 'windows',
354
+ }
355
+ end
356
+
357
+ private
358
+
359
+ # detect if service is enabled
360
+ def service_enabled?(service)
361
+ if !service['WMI'].nil? &&
362
+ !service['WMI']['StartMode'].nil? &&
363
+ service['WMI']['StartMode'] == 'Auto'
364
+ true
365
+ else
366
+ false
367
+ end
368
+ end
369
+
370
+ # detect if service is running
371
+ def service_running?(service)
372
+ if !service['Service']['Status'].nil? &&
373
+ service['Service']['Status'] == 4
374
+ true
375
+ else
376
+ false
377
+ end
378
+ end
379
+ end