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,75 @@
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
+
9
+ class SshConf < Inspec.resource(1)
10
+ name 'ssh_config'
11
+
12
+ def initialize(conf_path = nil, type = nil)
13
+ @conf_path = conf_path || '/etc/ssh/ssh_config'
14
+ typename = (@conf_path.include?('sshd') ? 'Server' : 'Client')
15
+ @type = type || "SSH #{typename} configuration #{conf_path}"
16
+ end
17
+
18
+ def content
19
+ read_content
20
+ end
21
+
22
+ def params(*opts)
23
+ opts.inject(read_params) do |res, nxt|
24
+ res.respond_to?(:key) ? res[nxt] : nil
25
+ end
26
+ end
27
+
28
+ def method_missing(name)
29
+ param = read_params[name.to_s]
30
+ return nil if param.nil?
31
+ # extract first value if we have only one value in array
32
+ return param[0] if param.length == 1
33
+ param
34
+ end
35
+
36
+ def to_s
37
+ 'SSH Configuration'
38
+ end
39
+
40
+ private
41
+
42
+ def read_content
43
+ return @content if defined?(@content)
44
+ file = inspec.file(@conf_path)
45
+ if !file.file?
46
+ return skip_resource "Can't find file \"#{@conf_path}\""
47
+ end
48
+
49
+ @content = file.content
50
+ if @content.empty? && file.size > 0
51
+ return skip_resource "Can't read file \"#{@conf_path}\""
52
+ end
53
+
54
+ @content
55
+ end
56
+
57
+ def read_params
58
+ return @params if defined?(@params)
59
+ return @params = {} if read_content.nil?
60
+ conf = SimpleConfig.new(
61
+ read_content,
62
+ assignment_re: /^\s*(\S+?)\s+(.*?)\s*$/,
63
+ multiple_values: true,
64
+ )
65
+ @params = conf.params
66
+ end
67
+ end
68
+
69
+ class SshdConf < SshConf
70
+ name 'sshd_config'
71
+
72
+ def initialize(path = nil)
73
+ super(path || '/etc/ssh/sshd_config')
74
+ end
75
+ end
@@ -0,0 +1,374 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+
5
+ # Usage:
6
+ #
7
+ # describe user('root') do
8
+ # it { should exist }
9
+ # its(:uid) { should eq 0 }
10
+ # its(:gid) { should eq 0 }
11
+ # its(:group) { should eq 'root' }
12
+ # its(:groups) { should eq ['root', 'wheel']}
13
+ # its(:home) { should eq '/root' }
14
+ # its(:shell) { should eq '/bin/bash' }
15
+ # its(:mindays) { should eq 0 }
16
+ # its(:maxdays) { should eq 99 }
17
+ # its(:warndays) { should eq 5 }
18
+ # end
19
+ #
20
+ # The following Serverspec matchers are deprecated in favor for direct value access
21
+ #
22
+ # describe user('root') do
23
+ # it { should belong_to_group 'root' }
24
+ # it { should have_uid 0 }
25
+ # it { should have_home_directory '/root' }
26
+ # it { should have_login_shell '/bin/bash' }
27
+ # its(:minimum_days_between_password_change) { should eq 0 }
28
+ # its(:maximum_days_between_password_change) { should eq 99 }
29
+ # end
30
+
31
+ # ServerSpec tests that are not supported:
32
+ #
33
+ # describe user('root') do
34
+ # it { should have_authorized_key 'ssh-rsa ADg54...3434 user@example.local' }
35
+ # its(:encrypted_password) { should eq 1234 }
36
+ # end
37
+
38
+ require 'utils/parser'
39
+ require 'utils/convert'
40
+
41
+ class User < Inspec.resource(1)
42
+ name 'user'
43
+
44
+ def initialize(user)
45
+ @user = user
46
+
47
+ # select package manager
48
+ @user_provider = nil
49
+ case inspec.os[:family]
50
+ when 'ubuntu', 'debian', 'redhat', 'fedora', 'centos', 'arch', 'opensuse'
51
+ @user_provider = LinuxUser.new(inspec)
52
+ when 'windows'
53
+ @user_provider = WindowsUser.new(inspec)
54
+ when 'darwin'
55
+ @user_provider = DarwinUser.new(inspec)
56
+ when 'freebsd'
57
+ @user_provider = FreeBSDUser.new(inspec)
58
+ else
59
+ return skip_resource 'The `user` resource is not supported on your OS yet.'
60
+ end
61
+ end
62
+
63
+ def exists?
64
+ !identiy.nil? && !identiy[:user].nil?
65
+ end
66
+
67
+ def uid
68
+ identiy.nil? ? nil : identiy[:uid]
69
+ end
70
+
71
+ def gid
72
+ identiy.nil? ? nil : identiy[:gid]
73
+ end
74
+
75
+ def group
76
+ identiy.nil? ? nil : identiy[:group]
77
+ end
78
+
79
+ def groups
80
+ identiy.nil? ? nil : identiy[:groups]
81
+ end
82
+
83
+ def home
84
+ meta_info.nil? ? nil : meta_info[:home]
85
+ end
86
+
87
+ def shell
88
+ meta_info.nil? ? nil : meta_info[:shell]
89
+ end
90
+
91
+ # returns the minimum days between password changes
92
+ def mindays
93
+ credentials.nil? ? nil : credentials[:mindays]
94
+ end
95
+
96
+ # returns the maximum days between password changes
97
+ def maxdays
98
+ credentials.nil? ? nil : credentials[:maxdays]
99
+ end
100
+
101
+ # returns the days for password change warning
102
+ def warndays
103
+ credentials.nil? ? nil : credentials[:warndays]
104
+ end
105
+
106
+ # implement 'mindays' method to be compatible with serverspec
107
+ def minimum_days_between_password_change
108
+ deprecated('minimum_days_between_password_change', "Please use 'its(:mindays)'")
109
+ mindays
110
+ end
111
+
112
+ # implement 'maxdays' method to be compatible with serverspec
113
+ def maximum_days_between_password_change
114
+ deprecated('maximum_days_between_password_change', "Please use 'its(:maxdays)'")
115
+ maxdays
116
+ end
117
+
118
+ # implements rspec has matcher, to be compatible with serverspec
119
+ # @see: https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers/built_in/has.rb
120
+ def has_uid?(compare_uid)
121
+ deprecated('has_uid?')
122
+ uid == compare_uid
123
+ end
124
+
125
+ def has_home_directory?(compare_home)
126
+ deprecated('has_home_directory?', "Please use 'its(:home)'")
127
+ home == compare_home
128
+ end
129
+
130
+ def has_login_shell?(compare_shell)
131
+ deprecated('has_login_shell?', "Please use 'its(:shell)'")
132
+ shell == compare_shell
133
+ end
134
+
135
+ def has_authorized_key?(_compare_key)
136
+ deprecated('has_authorized_key?')
137
+ fail NotImplementedError
138
+ end
139
+
140
+ def deprecated(name, alternative = nil)
141
+ warn "[DEPRECATION] #{name} is deprecated. #{alternative}"
142
+ end
143
+
144
+ def to_s
145
+ "User #{@user}"
146
+ end
147
+
148
+ private
149
+
150
+ def identiy
151
+ return @id_cache if defined?(@id_cache)
152
+ @id_cache = @user_provider.identity(@user) if !@user_provider.nil?
153
+ end
154
+
155
+ def meta_info
156
+ return @meta_cache if defined?(@meta_cache)
157
+ @meta_cache = @user_provider.meta_info(@user) if !@user_provider.nil?
158
+ end
159
+
160
+ def credentials
161
+ return @cred_cache if defined?(@cred_cache)
162
+ @cred_cache = @user_provider.credentials(@user) if !@user_provider.nil?
163
+ end
164
+ end
165
+
166
+ class UserInfo
167
+ include Converter
168
+
169
+ attr_reader :inspec
170
+ def initialize(inspec)
171
+ @inspec = inspec
172
+ end
173
+
174
+ def credentials(_username)
175
+ end
176
+ end
177
+
178
+ # implements generic unix id handling
179
+ class UnixUser < UserInfo
180
+ # parse one id entry like '0(wheel)''
181
+ def parse_value(line)
182
+ SimpleConfig.new(
183
+ line,
184
+ line_separator: ',',
185
+ assignment_re: /^\s*([^\(]*?)\s*\(\s*(.*?)\)*$/,
186
+ group_re: nil,
187
+ multiple_values: false,
188
+ ).params
189
+ end
190
+
191
+ # extracts the identity
192
+ def identity(username)
193
+ cmd = inspec.command("id #{username}")
194
+ return nil if cmd.exit_status != 0
195
+
196
+ # parse words
197
+ params = SimpleConfig.new(
198
+ cmd.stdout.chomp,
199
+ line_separator: ' ',
200
+ assignment_re: /^\s*([^=]*?)\s*=\s*(.*?)\s*$/,
201
+ group_re: nil,
202
+ multiple_values: false,
203
+ ).params
204
+
205
+ {
206
+ uid: convert_to_i(parse_value(params['uid']).keys[0]),
207
+ user: parse_value(params['uid']).values[0],
208
+ gid: convert_to_i(parse_value(params['gid']).keys[0]),
209
+ group: parse_value(params['gid']).values[0],
210
+ groups: parse_value(params['groups']).values,
211
+ }
212
+ end
213
+ end
214
+
215
+ class LinuxUser < UnixUser
216
+ include ContentParser
217
+
218
+ def meta_info(username)
219
+ cmd = inspec.command("getent passwd #{username}")
220
+ return nil if cmd.exit_status != 0
221
+ # returns: root:x:0:0:root:/root:/bin/bash
222
+ passwd = parse_passwd_line(cmd.stdout.chomp)
223
+ {
224
+ home: passwd['home'],
225
+ shell: passwd['shell'],
226
+ }
227
+ end
228
+
229
+ def credentials(username)
230
+ cmd = inspec.command("chage -l #{username}")
231
+ return nil if cmd.exit_status != 0
232
+
233
+ params = SimpleConfig.new(
234
+ cmd.stdout.chomp,
235
+ assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
236
+ group_re: nil,
237
+ multiple_values: false,
238
+ ).params
239
+
240
+ {
241
+ mindays: convert_to_i(params['Minimum number of days between password change']),
242
+ maxdays: convert_to_i(params['Maximum number of days between password change']),
243
+ warndays: convert_to_i(params['Number of days of warning before password expires']),
244
+ }
245
+ end
246
+ end
247
+
248
+ # we do not use 'finger' for MacOS, because it is harder to parse data with it
249
+ # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/fingerd.8.html
250
+ # instead we use 'dscl' to request user data
251
+ # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dscl.1.html
252
+ # @see http://superuser.com/questions/592921/mac-osx-users-vs-dscl-command-to-list-user
253
+ class DarwinUser < UnixUser
254
+ def meta_info(username)
255
+ cmd = inspec.command("dscl -q . -read /Users/#{username} NFSHomeDirectory PrimaryGroupID RecordName UniqueID UserShell")
256
+ return nil if cmd.exit_status != 0
257
+
258
+ params = SimpleConfig.new(
259
+ cmd.stdout.chomp,
260
+ assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
261
+ group_re: nil,
262
+ multiple_values: false,
263
+ ).params
264
+
265
+ {
266
+ home: params['NFSHomeDirectory'],
267
+ shell: params['UserShell'],
268
+ }
269
+ end
270
+ end
271
+
272
+ # FreeBSD recommends to use the 'pw' command for user management
273
+ # @see: https://www.freebsd.org/doc/handbook/users-synopsis.html
274
+ # @see: https://www.freebsd.org/cgi/man.cgi?pw(8)
275
+ # It offers the following commands:
276
+ # - adduser(8) The recommended command-line application for adding new users.
277
+ # - rmuser(8) The recommended command-line application for removing users.
278
+ # - chpass(1) A flexible tool for changing user database information.
279
+ # - passwd(1) The command-line tool to change user passwords.
280
+ class FreeBSDUser < UnixUser
281
+ include ContentParser
282
+
283
+ def meta_info(username)
284
+ cmd = inspec.command("pw usershow #{username} -7")
285
+ return nil if cmd.exit_status != 0
286
+ # returns: root:*:0:0:Charlie &:/root:/bin/csh
287
+ passwd = parse_passwd_line(cmd.stdout.chomp)
288
+ {
289
+ home: passwd['home'],
290
+ shell: passwd['shell'],
291
+ }
292
+ end
293
+ end
294
+
295
+ # For now, we stick with WMI Win32_UserAccount
296
+ # @see https://msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx
297
+ # @see https://msdn.microsoft.com/en-us/library/aa394153(v=vs.85).aspx
298
+ #
299
+ # using Get-AdUser would be the best command for domain machines, but it will not be installed
300
+ # on client machines by default
301
+ # @see https://technet.microsoft.com/en-us/library/ee617241.aspx
302
+ # @see https://technet.microsoft.com/en-us/library/hh509016(v=WS.10).aspx
303
+ # @see http://woshub.com/get-aduser-getting-active-directory-users-data-via-powershell/
304
+ # @see http://stackoverflow.com/questions/17548523/the-term-get-aduser-is-not-recognized-as-the-name-of-a-cmdlet
305
+ #
306
+ # Just for reference, we could also use ADSI (Active Directory Service Interfaces)
307
+ # @see https://mcpmag.com/articles/2015/04/15/reporting-on-local-accounts.aspx
308
+ class WindowsUser < UserInfo
309
+ # parse windows account name
310
+ def parse_windows_account(username)
311
+ account = username.split('\\')
312
+ name = account.pop
313
+ domain = account.pop if account.size > 0
314
+ [name, domain]
315
+ end
316
+
317
+ def identity(username)
318
+ # extract domain/user information
319
+ account, domain = parse_windows_account(username)
320
+
321
+ # TODO: escape content
322
+ if !domain.nil?
323
+ filter = "Name = '#{account}' and Domain = '#{domain}'"
324
+ else
325
+ filter = "Name = '#{account}' and LocalAccount = true"
326
+ end
327
+
328
+ script = <<-EOH
329
+ # find user
330
+ $user = Get-WmiObject Win32_UserAccount -filter "#{filter}"
331
+ # get related groups
332
+ $groups = $user.GetRelated('Win32_Group') | Select-Object -Property Caption, Domain, Name, LocalAccount, SID, SIDType, Status
333
+ # filter user information
334
+ $user = $user | Select-Object -Property Caption, Description, Domain, Name, LocalAccount, Lockout, PasswordChangeable, PasswordExpires, PasswordRequired, SID, SIDType, Status
335
+ # build response object
336
+ New-Object -Type PSObject | `
337
+ Add-Member -MemberType NoteProperty -Name User -Value ($user) -PassThru | `
338
+ Add-Member -MemberType NoteProperty -Name Groups -Value ($groups) -PassThru | `
339
+ ConvertTo-Json
340
+ EOH
341
+
342
+ cmd = inspec.script(script)
343
+
344
+ # cannot rely on exit code for now, successful command returns exit code 1
345
+ # return nil if cmd.exit_status != 0, try to parse json
346
+ begin
347
+ params = JSON.parse(cmd.stdout)
348
+ rescue JSON::ParserError => _e
349
+ return nil
350
+ end
351
+
352
+ user = params['User']['Caption'] unless params['User'].nil?
353
+ groups = params['Groups']
354
+ # if groups is no array, generate one
355
+ groups = [groups] if !groups.is_a?(Array)
356
+ groups = groups.map { |grp| grp['Caption'] } unless params['Groups'].nil?
357
+
358
+ {
359
+ uid: nil,
360
+ user: user,
361
+ gid: nil,
362
+ group: nil,
363
+ groups: groups,
364
+ }
365
+ end
366
+
367
+ # not implemented yet
368
+ def meta_info(_username)
369
+ {
370
+ home: nil,
371
+ shell: nil,
372
+ }
373
+ end
374
+ end
@@ -0,0 +1,77 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+
5
+ # check for a Windows feature
6
+ # Usage:
7
+ # describe windows_feature('DHCP Server') do
8
+ # it{ should be_installed }
9
+ # end
10
+ #
11
+ # deprecated serverspec syntax:
12
+ # describe windows_feature('IIS-Webserver') do
13
+ # it{ should be_installed.by("dism") }
14
+ # end
15
+ #
16
+ # describe windows_feature('Web-Webserver') do
17
+ # it{ should be_installed.by("powershell") }
18
+ # end
19
+ #
20
+ # This implementation uses the Get-WindowsFeature commandlet:
21
+ # Get-WindowsFeature | Where-Object {$_.Name -eq 'XPS Viewer' -or $_.DisplayName -eq 'XPS Viewe
22
+ # r'} | Select-Object -Property Name,DisplayName,Description,Installed,InstallState | ConvertTo-Json
23
+ # {
24
+ # "Name": "XPS-Viewer",
25
+ # "DisplayName": "XPS Viewer",
26
+ # "Description": "The XPS Viewer is used to read, set permissions for, and digitally sign XPS documents.",
27
+ # "Installed": false,
28
+ # "InstallState": 0
29
+ # }
30
+ class WindowsFeature < Inspec.resource(1)
31
+ name 'windows_feature'
32
+
33
+ def initialize(feature)
34
+ @feature = feature
35
+ @cache = nil
36
+
37
+ # verify that this resource is only supported on Windows
38
+ return skip_resource 'The `windows_feature` resource is not supported on your OS.' if inspec.os[:family] != 'windows'
39
+ end
40
+
41
+ # returns true if the package is installed
42
+ def installed?(_provider = nil, _version = nil)
43
+ info[:installed] == true
44
+ end
45
+
46
+ # returns the package description
47
+ def info
48
+ return @cache if !@cache.nil?
49
+ features_cmd = "Get-WindowsFeature | Where-Object {$_.Name -eq '#{@feature}' -or $_.DisplayName -eq '#{@feature}'} | Select-Object -Property Name,DisplayName,Description,Installed,InstallState | ConvertTo-Json"
50
+ cmd = inspec.command(features_cmd)
51
+
52
+ @cache = {
53
+ name: @feature,
54
+ type: 'windows-feature',
55
+ }
56
+
57
+ # cannot rely on exit code for now, successful command returns exit code 1
58
+ # return nil if cmd.exit_status != 0
59
+ # try to parse json
60
+ begin
61
+ params = JSON.parse(cmd.stdout)
62
+ rescue JSON::ParserError => _e
63
+ return @cache
64
+ end
65
+
66
+ @cache = {
67
+ name: params['Name'],
68
+ description: params['Description'],
69
+ installed: params['Installed'],
70
+ type: 'windows-feature',
71
+ }
72
+ end
73
+
74
+ def to_s
75
+ "Windows Feature '#{@feature}'"
76
+ end
77
+ end