inspec 1.0.0.beta3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 75fea2e790e0dcea3951df73e3ad7976a5e8e659
4
- data.tar.gz: b7bd822fd9b85f6da803b2078481089cb9801b83
3
+ metadata.gz: 6c1825c5508e4853cf4bc1446a5b2bd679d9d1be
4
+ data.tar.gz: 6ffbe626a987b38ea03561d8bd18681803d89aaf
5
5
  SHA512:
6
- metadata.gz: 705b7694c8d6dbecea6646f6f71c691774ae5946106e9381f47fdfb276848e7e0b1ca1c80e7c3db5ee3dbf6b8958ed0a1fa94b6f75d5abea19ac555bfc44f095
7
- data.tar.gz: fd38cbdb8d6f3d9063283a9c9f774e7d0f412e5bc1d697dc9cbeeed29dd911b239470b18d273d691fe7ddf53c3699359895cdb8a0b354f6a6f38205ae890d738
6
+ metadata.gz: 4bb3e9f9c6295dcb29e500b134e5b264ac239f8270cb695dd6c28aeefa7fc9b65604e93b9c144f2754536988a8c1220dc31df12add23729fcaea9eabecbd6b11
7
+ data.tar.gz: 5916f874af8a593d5b08e7456bacf42b5ed7c6dadf73b243c4e0d6d675af470f7aa2928abb603da4a32d97c76ded9e1354e6ab05eac61ffd85d4d0030e7fe60f
@@ -1,7 +1,39 @@
1
1
  # Change Log
2
2
 
3
- ## [1.0.0.beta3](https://github.com/chef/inspec/tree/1.0.0.beta3) (2016-09-25)
4
- [Full Changelog](https://github.com/chef/inspec/compare/v1.0.0.beta2...1.0.0.beta3)
3
+ ## [1.0.0](https://github.com/chef/inspec/tree/1.0.0) (2016-09-26)
4
+ [Full Changelog](https://github.com/chef/inspec/compare/v1.0.0.beta3...1.0.0)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - InSpec OS package [\#646](https://github.com/chef/inspec/issues/646)
9
+ - replace wmi win32\_useraccount with adsi users [\#1149](https://github.com/chef/inspec/pull/1149) ([chris-rock](https://github.com/chris-rock))
10
+
11
+ **Fixed bugs:**
12
+
13
+ - README.md has broken link to non-existent file [\#1136](https://github.com/chef/inspec/issues/1136)
14
+
15
+ **Merged pull requests:**
16
+
17
+ - update omnibus images [\#1164](https://github.com/chef/inspec/pull/1164) ([chris-rock](https://github.com/chris-rock))
18
+ - website / tutorial interaction [\#1163](https://github.com/chef/inspec/pull/1163) ([chris-rock](https://github.com/chris-rock))
19
+ - fix buttons on community page [\#1162](https://github.com/chef/inspec/pull/1162) ([arlimus](https://github.com/arlimus))
20
+ - fix alignment of community buttons [\#1161](https://github.com/chef/inspec/pull/1161) ([arlimus](https://github.com/arlimus))
21
+ - Fix require\_controls DSL method [\#1160](https://github.com/chef/inspec/pull/1160) ([stevendanna](https://github.com/stevendanna))
22
+ - Document the require\_resource function [\#1158](https://github.com/chef/inspec/pull/1158) ([stevendanna](https://github.com/stevendanna))
23
+ - fix css in docs search [\#1157](https://github.com/chef/inspec/pull/1157) ([arlimus](https://github.com/arlimus))
24
+ - update www readme for releasing the site [\#1156](https://github.com/chef/inspec/pull/1156) ([arlimus](https://github.com/arlimus))
25
+ - Fix minor typo in sys\_info documentation [\#1155](https://github.com/chef/inspec/pull/1155) ([stevendanna](https://github.com/stevendanna))
26
+ - fix outdated link in readme [\#1154](https://github.com/chef/inspec/pull/1154) ([arlimus](https://github.com/arlimus))
27
+ - fix minor website bugs [\#1153](https://github.com/chef/inspec/pull/1153) ([arlimus](https://github.com/arlimus))
28
+ - clean www before releasing [\#1152](https://github.com/chef/inspec/pull/1152) ([arlimus](https://github.com/arlimus))
29
+ - add docs to the website [\#1151](https://github.com/chef/inspec/pull/1151) ([arlimus](https://github.com/arlimus))
30
+ - return empty array for known privileges [\#1150](https://github.com/chef/inspec/pull/1150) ([chris-rock](https://github.com/chris-rock))
31
+ - Extend example for parse\_config.rb [\#1148](https://github.com/chef/inspec/pull/1148) ([nvtkaszpir](https://github.com/nvtkaszpir))
32
+ - Bump lockfile version to 1.0 [\#1141](https://github.com/chef/inspec/pull/1141) ([stevendanna](https://github.com/stevendanna))
33
+ - Improve error messages from compliance fetcher [\#1126](https://github.com/chef/inspec/pull/1126) ([stevendanna](https://github.com/stevendanna))
34
+
35
+ ## [v1.0.0.beta3](https://github.com/chef/inspec/tree/v1.0.0.beta3) (2016-09-25)
36
+ [Full Changelog](https://github.com/chef/inspec/compare/v1.0.0.beta2...v1.0.0.beta3)
5
37
 
6
38
  **Implemented enhancements:**
7
39
 
data/README.md CHANGED
@@ -18,7 +18,7 @@ describe inetd_conf do
18
18
  end
19
19
  ```
20
20
 
21
- InSpec makes it easy to run your tests wherever you need. More options listed here: https://github.com/chef/inspec/blob/master/docs/cli.rst
21
+ InSpec makes it easy to run your tests wherever you need. More options are found in our [CLI docs](http://inspec.io/docs/reference/cli/).
22
22
 
23
23
  ```bash
24
24
  # run test locally
@@ -4,6 +4,9 @@ title: InSpec and friends
4
4
 
5
5
  # InSpec and friends
6
6
 
7
+ This page looks at projects that are similar to InSpec to explain how they
8
+ relate to each other.
9
+
7
10
  ## RSpec
8
11
 
9
12
  RSpec is an awesome framework that is widely used to test Ruby code. It
@@ -37,7 +40,7 @@ control "sshd-11" do
37
40
  end
38
41
  ```
39
42
 
40
- ## Serverspec
43
+ ## Serverspec
41
44
 
42
45
  Serverspec can be credited as the first extension of RSpec that enabled
43
46
  users to run RSpec tests on servers to verify deployed artifacts. It was
@@ -242,6 +242,20 @@ For example, to require that controls `cis-fs-2.1` and `cis-fs-2.2` be loaded fr
242
242
 
243
243
  end
244
244
 
245
+
246
+ ## require_resource
247
+
248
+ By default, all of the resources from a listed dependency are available
249
+ for use in your profile. If two of your dependencies provide a resource with
250
+ the same name, you can use the `require_resource` DSL function to
251
+ disambiguate the two:
252
+
253
+ require_resource(profile: 'my_dep', resource: 'my_res',
254
+ as: 'my_res2')
255
+
256
+ This will allow you to reference the resource `my_res` from the
257
+ profile `my_dep` using the name `my_res2`.
258
+
245
259
  # Profile Attributes
246
260
 
247
261
  Attributes may be used in profiles to define secrets, such as user names and passwords, that should not otherwise be stored in plain-text in a cookbook. First specify a variable in the control for each secret, then add the secret to a Yaml file located on the local machine, and then run `inspec exec` and specify the path to that Yaml file using the `--attrs` attribute.
@@ -4,6 +4,7 @@
4
4
 
5
5
  require 'uri'
6
6
  require 'inspec/fetcher'
7
+ require 'inspec/errors'
7
8
 
8
9
  # InSpec Target Helper for Chef Compliance
9
10
  # reuses UrlHelper, but it knows the target server and the access token already
@@ -24,11 +25,23 @@ module Compliance
24
25
 
25
26
  # check if we have a compliance token
26
27
  config = Compliance::Configuration.new
27
- return nil if config['token'].nil?
28
+ if config['token'].nil?
29
+ fail Inspec::FetcherFailure, <<EOF
30
+
31
+ Cannot fetch #{uri} because your compliance token has not been
32
+ configured.
33
+
34
+ Please login using
35
+
36
+ inspec compliance login https://your_compliance_server --user admin --insecure --token 'PASTE TOKEN HERE'
37
+ EOF
38
+ end
28
39
 
29
40
  # verifies that the target e.g base/ssh exists
30
41
  profile = uri.host + uri.path
31
- Compliance::API.exist?(config, profile)
42
+ if !Compliance::API.exist?(config, profile)
43
+ fail Inpsec::FetcherFailure, "The compliance profile #{profile} was not found on the configured compliance server"
44
+ end
32
45
  new(target_url(profile, config), config)
33
46
  rescue URI::Error => _e
34
47
  nil
@@ -132,6 +132,16 @@ module Inspec
132
132
  Logger.const_get(l.upcase)
133
133
  end
134
134
 
135
+ def pretty_handle_exception(exception)
136
+ case exception
137
+ when Inspec::Error
138
+ $stderr.puts exception.message
139
+ exit(1)
140
+ else
141
+ raise exception # rubocop:disable Style/SignalException
142
+ end
143
+ end
144
+
135
145
  def configure_logger(o)
136
146
  #
137
147
  # TODO(ssd): This is a big gross, but this configures the
@@ -48,6 +48,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
48
48
  fdst = File.expand_path(dst)
49
49
  File.write(fdst, JSON.dump(profile.info))
50
50
  end
51
+ rescue StandardError => e
52
+ pretty_handle_exception(e)
51
53
  end
52
54
 
53
55
  desc 'check PATH', 'verify all tests at the specified PATH'
@@ -97,6 +99,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
97
99
  end
98
100
  end
99
101
  exit 1 unless result[:summary][:valid]
102
+ rescue StandardError => e
103
+ pretty_handle_exception(e)
100
104
  end
101
105
 
102
106
  desc 'vendor', 'Download all dependencies and generate a lockfile'
@@ -105,6 +109,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
105
109
  profile = Inspec::Profile.for_target('./', opts.merge(cache: Inspec::Cache.new(path)))
106
110
  lockfile = profile.generate_lockfile
107
111
  File.write('inspec.lock', lockfile.to_yaml)
112
+ rescue StandardError => e
113
+ pretty_handle_exception(e)
108
114
  end
109
115
 
110
116
  desc 'archive PATH', 'archive a profile to tar.gz (default) or zip'
@@ -136,6 +142,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
136
142
 
137
143
  # generate archive
138
144
  exit 1 unless profile.archive(opts)
145
+ rescue StandardError => e
146
+ pretty_handle_exception(e)
139
147
  end
140
148
 
141
149
  desc 'exec PATHS', 'run all test files at the specified PATH.'
@@ -147,6 +155,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
147
155
 
148
156
  # run tests
149
157
  run_tests(targets, o)
158
+ rescue StandardError => e
159
+ pretty_handle_exception(e)
150
160
  end
151
161
 
152
162
  desc 'detect', 'detect the target OS'
@@ -165,6 +175,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
165
175
  mark_text(res[item.to_sym]))
166
176
  }
167
177
  end
178
+ rescue StandardError => e
179
+ pretty_handle_exception(e)
168
180
  end
169
181
 
170
182
  desc 'shell', 'open an interactive debugging shell'
@@ -196,12 +208,16 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
196
208
  exit 0
197
209
  rescue RuntimeError, Train::UserError => e
198
210
  $stderr.puts e.message
211
+ rescue StandardError => e
212
+ pretty_handle_exception(e)
199
213
  end
200
214
 
201
215
  desc 'env', 'Output shell-appropriate completion configuration'
202
216
  def env(shell = nil)
203
217
  p = Inspec::EnvPrinter.new(self.class, shell)
204
218
  p.print_and_exit!
219
+ rescue StandardError => e
220
+ pretty_handle_exception(e)
205
221
  end
206
222
 
207
223
  desc 'version', 'prints the version of this tool'
@@ -4,8 +4,8 @@ require 'yaml'
4
4
  module Inspec
5
5
  class Lockfile
6
6
  # When we finalize this feature, we should set these to 1
7
- MINIMUM_SUPPORTED_VERSION = 0
8
- CURRENT_LOCKFILE_VERSION = 0
7
+ MINIMUM_SUPPORTED_VERSION = 1
8
+ CURRENT_LOCKFILE_VERSION = 1
9
9
 
10
10
  def self.from_dependency_set(dep_set)
11
11
  lockfile_content = {
@@ -32,14 +32,6 @@ lower than the minimum supported version #{MINIMUM_SUPPORTED_VERSION}.
32
32
  Please create a new lockfile for this project by running:
33
33
 
34
34
  inspec vendor
35
- EOF
36
- elsif version == 0
37
- # Remove this case once this feature stablizes
38
- $stderr.puts <<EOF
39
- WARNING: This is a version 0 lockfile. Thank you for trying the
40
- experimental dependency management feature. Please be aware you may
41
- need to regenerate this lockfile in future versions as the feature is
42
- currently in development.
43
35
  EOF
44
36
  elsif version > CURRENT_LOCKFILE_VERSION
45
37
  fail <<EOF
@@ -78,8 +70,8 @@ EOF
78
70
  # different entry points of the API.
79
71
  def parse_content_hash(lockfile_content_hash)
80
72
  case version
81
- when 0
82
- parse_content_hash_0(lockfile_content_hash)
73
+ when 1
74
+ parse_content_hash_1(lockfile_content_hash)
83
75
  else
84
76
  # If we've gotten here, there is likely a mistake in the
85
77
  # lockfile version validation in the constructor.
@@ -87,7 +79,7 @@ EOF
87
79
  end
88
80
  end
89
81
 
90
- def parse_content_hash_0(lockfile_content_hash)
82
+ def parse_content_hash_1(lockfile_content_hash)
91
83
  @deps = if lockfile_content_hash['depends']
92
84
  lockfile_content_hash['depends'].map { |i| symbolize_keys(i) }
93
85
  end
@@ -53,12 +53,15 @@ EOF
53
53
  end
54
54
 
55
55
  def self.filter_included_controls(context, profile, &block)
56
- include_ctx = profile.runner_context
56
+ mock = Inspec::Backend.create({ backend: 'mock' })
57
+ include_ctx = Inspec::ProfileContext.for_profile(profile, mock, {})
57
58
  include_ctx.load(block) if block_given?
58
59
  # remove all rules that were not registered
59
- context.rules.keys.each do |id|
60
- unless include_ctx.rules[id]
61
- context.rules[id] = nil
60
+ context.all_rules.each do |r|
61
+ id = Inspec::Rule.rule_id(r)
62
+ fid = Inspec::Rule.profile_id(r) + '/' + id
63
+ unless include_ctx.rules[id] || include_ctx.rules[fid]
64
+ context.remove_rule(fid)
62
65
  end
63
66
  end
64
67
  end
@@ -9,4 +9,5 @@ module Inspec
9
9
  class CyclicDependencyError < Error; end
10
10
  class UnsatisfiedVersionSpecification < Error; end
11
11
  class DuplicateDep < Error; end
12
+ class FetcherFailure < Error; end
12
13
  end
@@ -388,6 +388,7 @@ module Inspec
388
388
  params[:groups] = groups = {}
389
389
  prefix = @source_reader.target.prefix || ''
390
390
  tests.each do |rule|
391
+ next if rule.nil?
391
392
  f = load_rule_filepath(prefix, rule)
392
393
  load_rule(rule, f, controls, groups)
393
394
  end
@@ -67,6 +67,13 @@ module Inspec
67
67
  @conf['profile'].supports_os?
68
68
  end
69
69
 
70
+ def remove_rule(id)
71
+ @rules[id] = nil if @rules.key?(id)
72
+ @control_subcontexts.each do |c|
73
+ c.remove_rule(id)
74
+ end
75
+ end
76
+
70
77
  def all_controls
71
78
  ret = @rules.values
72
79
  ret += @control_subcontexts.map(&:all_rules).flatten
@@ -86,7 +86,7 @@ module Inspec
86
86
  end
87
87
 
88
88
  all_controls.each do |rule|
89
- register_rule(rule)
89
+ register_rule(rule) unless rule.nil?
90
90
  end
91
91
  end
92
92
 
@@ -4,5 +4,5 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  module Inspec
7
- VERSION = '1.0.0.beta3'.freeze
7
+ VERSION = '1.0.0'.freeze
8
8
  end
@@ -19,10 +19,27 @@ module Inspec::Resources
19
19
  desc 'Use the parse_config InSpec audit resource to test arbitrary configuration files.'
20
20
  example "
21
21
  output = command('some-command').stdout
22
-
23
22
  describe parse_config(output, { data_config_option: value } ) do
24
23
  its('setting') { should eq 1 }
25
24
  end
25
+
26
+ output2 = command('curl http://127.0.0.1/php_status').stdout
27
+ # php status is in format 'key : value', and we do not allow for multiple values
28
+ options2 = {
29
+ assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
30
+ multiple_values: false
31
+ }
32
+
33
+ describe parse_config(output2, options2) do
34
+ its('pool') { should eq 'www'}
35
+ its('process manager') { should eq process_manager }
36
+ end
37
+
38
+ # getting specific key from the output above, convert it to integer and then compare
39
+ # make sure 'listen queue' is below 100
40
+ describe parse_config(output2, options2 ).params['listen queue'].to_i do
41
+ it { should be < 100 }
42
+ end
26
43
  "
27
44
 
28
45
  attr_reader :content
@@ -16,6 +16,57 @@
16
16
  require 'hashie'
17
17
 
18
18
  module Inspec::Resources
19
+ # known and supported MS privilege rights
20
+ # @see https://technet.microsoft.com/en-us/library/dd277311.aspx
21
+ # @see https://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx
22
+ MS_PRIVILEGES_RIGHTS = [
23
+ 'SeNetworkLogonRight',
24
+ 'SeBackupPrivilege',
25
+ 'SeChangeNotifyPrivilege',
26
+ 'SeSystemtimePrivilege',
27
+ 'SeCreatePagefilePrivilege',
28
+ 'SeDebugPrivilege',
29
+ 'SeRemoteShutdownPrivilege',
30
+ 'SeAuditPrivilege',
31
+ 'SeIncreaseQuotaPrivilege',
32
+ 'SeIncreaseBasePriorityPrivilege',
33
+ 'SeLoadDriverPrivilege',
34
+ 'SeBatchLogonRight',
35
+ 'SeServiceLogonRight',
36
+ 'SeInteractiveLogonRight',
37
+ 'SeSecurityPrivilege',
38
+ 'SeSystemEnvironmentPrivilege',
39
+ 'SeProfileSingleProcessPrivilege',
40
+ 'SeSystemProfilePrivilege',
41
+ 'SeAssignPrimaryTokenPrivilege',
42
+ 'SeRestorePrivilege',
43
+ 'SeShutdownPrivilege',
44
+ 'SeTakeOwnershipPrivilege',
45
+ 'SeUndockPrivilege',
46
+ 'SeManageVolumePrivilege',
47
+ 'SeRemoteInteractiveLogonRight',
48
+ 'SeImpersonatePrivilege',
49
+ 'SeCreateGlobalPrivilege',
50
+ 'SeIncreaseWorking',
51
+ 'SeTimeZonePrivilege',
52
+ 'SeCreateSymbolicLinkPrivilege',
53
+ 'SeDenyNetworkLogonRight', # Deny access to this computer from the network
54
+ 'SeDenyInteractiveLogonRight', # Deny logon locally
55
+ 'SeDenyBatchLogonRight', # Deny logon as a batch job
56
+ 'SeDenyServiceLogonRight', # Deny logon as a service
57
+ 'SeTcbPrivilege',
58
+ 'SeMachineAccountPrivilege',
59
+ 'SeCreateTokenPrivilege',
60
+ 'SeCreatePermanentPrivilege',
61
+ 'SeEnableDelegationPrivilege',
62
+ 'SeLockMemoryPrivilege',
63
+ 'SeSyncAgentPrivilege',
64
+ 'SeUnsolicitedInputPrivilege',
65
+ 'SeTrustedCredManAccessPrivilege',
66
+ 'SeRelabelPrivilege', # the privilege to change a Windows integrity label (new to Windows Vista)
67
+ 'SeDenyRemoteInteractiveLogonRight', # Deny logon through Terminal Services
68
+ ].freeze
69
+
19
70
  class SecurityPolicy < Inspec.resource(1)
20
71
  name 'security_policy'
21
72
  desc 'Use the security_policy InSpec audit resource to test security policies on the Microsoft Windows platform.'
@@ -42,6 +93,9 @@ module Inspec::Resources
42
93
  # deep search for hash key
43
94
  params.extend Hashie::Extensions::DeepFind
44
95
  res = params.deep_find(name.to_s)
96
+
97
+ # return an empty array if configuration does not include rights configuration
98
+ return [] if res.nil? && MS_PRIVILEGES_RIGHTS.include?(name.to_s)
45
99
  res
46
100
  end
47
101
 
@@ -6,7 +6,7 @@ module Inspec::Resources
6
6
 
7
7
  desc 'Use the user InSpec system resource to test for operating system properties.'
8
8
  example "
9
- describe sysinfo do
9
+ describe sys_info do
10
10
  its('hostname') { should eq 'example.com' }
11
11
  end
12
12
  "
@@ -547,21 +547,12 @@ module Inspec::Resources
547
547
  end
548
548
  end
549
549
 
550
- # For now, we stick with WMI Win32_UserAccount
550
+ # This optimization was inspired by
551
+ # @see https://mcpmag.com/articles/2015/04/15/reporting-on-local-accounts.aspx
552
+ # Alternative solutions are WMI Win32_UserAccount
551
553
  # @see https://msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx
552
554
  # @see https://msdn.microsoft.com/en-us/library/aa394153(v=vs.85).aspx
553
- #
554
- # using Get-AdUser would be the best command for domain machines, but it will not be installed
555
- # on client machines by default
556
- # @see https://technet.microsoft.com/en-us/library/ee617241.aspx
557
- # @see https://technet.microsoft.com/en-us/library/hh509016(v=WS.10).aspx
558
- # @see http://woshub.com/get-aduser-getting-active-directory-users-data-via-powershell/
559
- # @see http://stackoverflow.com/questions/17548523/the-term-get-aduser-is-not-recognized-as-the-name-of-a-cmdlet
560
- #
561
- # Just for reference, we could also use ADSI (Active Directory Service Interfaces)
562
- # @see https://mcpmag.com/articles/2015/04/15/reporting-on-local-accounts.aspx
563
555
  class WindowsUser < UserInfo
564
- # parse windows account name
565
556
  def parse_windows_account(username)
566
557
  account = username.split('\\')
567
558
  name = account.pop
@@ -570,67 +561,92 @@ module Inspec::Resources
570
561
  end
571
562
 
572
563
  def identity(username)
573
- # extract domain/user information
574
- account, domain = parse_windows_account(username)
575
-
576
- # TODO: escape content
577
- if !domain.nil?
578
- filter = "Name = '#{account}' and Domain = '#{domain}'"
579
- else
580
- filter = "Name = '#{account}' and LocalAccount = true"
581
- end
564
+ # TODO: we look for local users only at this point
565
+ name, _domain = parse_windows_account(username)
566
+ return if collect_user_details.nil?
567
+ res = collect_user_details.select { |user| user[:username] == name }
568
+ res[0] if res.length > 0
569
+ end
582
570
 
571
+ def list_users
572
+ collect_user_details.map { |user| user[:username] }
573
+ end
574
+
575
+ # https://msdn.microsoft.com/en-us/library/aa746340(v=vs.85).aspx
576
+ def collect_user_details # rubocop:disable Metrics/MethodLength
577
+ return @users_cache if defined?(@users_cache)
583
578
  script = <<-EOH
584
- # find user
585
- $user = Get-WmiObject Win32_UserAccount -filter "#{filter}"
586
- # get related groups
587
- $groups = $user.GetRelated('Win32_Group') | Select-Object -Property Caption, Domain, Name, LocalAccount, SID, SIDType, Status
588
- # filter user information
589
- $user = $user | Select-Object -Property Caption, Description, Domain, Name, LocalAccount, Lockout, PasswordChangeable, PasswordExpires, PasswordRequired, SID, SIDType, Status, Disabled
590
- # build response object
591
- New-Object -Type PSObject | `
592
- Add-Member -MemberType NoteProperty -Name User -Value ($user) -PassThru | `
593
- Add-Member -MemberType NoteProperty -Name Groups -Value ($groups) -PassThru | `
594
- ConvertTo-Json
579
+ Function ConvertTo-SID { Param([byte[]]$BinarySID)
580
+ (New-Object System.Security.Principal.SecurityIdentifier($BinarySID,0)).Value
581
+ }
582
+
583
+ Function Convert-UserFlag { Param ($UserFlag)
584
+ $List = @()
585
+ Switch ($UserFlag) {
586
+ ($UserFlag -BOR 0x0001) { $List += 'SCRIPT' }
587
+ ($UserFlag -BOR 0x0002) { $List += 'ACCOUNTDISABLE' }
588
+ ($UserFlag -BOR 0x0008) { $List += 'HOMEDIR_REQUIRED' }
589
+ ($UserFlag -BOR 0x0010) { $List += 'LOCKOUT' }
590
+ ($UserFlag -BOR 0x0020) { $List += 'PASSWD_NOTREQD' }
591
+ ($UserFlag -BOR 0x0040) { $List += 'PASSWD_CANT_CHANGE' }
592
+ ($UserFlag -BOR 0x0080) { $List += 'ENCRYPTED_TEXT_PWD_ALLOWED' }
593
+ ($UserFlag -BOR 0x0100) { $List += 'TEMP_DUPLICATE_ACCOUNT' }
594
+ ($UserFlag -BOR 0x0200) { $List += 'NORMAL_ACCOUNT' }
595
+ ($UserFlag -BOR 0x0800) { $List += 'INTERDOMAIN_TRUST_ACCOUNT' }
596
+ ($UserFlag -BOR 0x1000) { $List += 'WORKSTATION_TRUST_ACCOUNT' }
597
+ ($UserFlag -BOR 0x2000) { $List += 'SERVER_TRUST_ACCOUNT' }
598
+ ($UserFlag -BOR 0x10000) { $List += 'DONT_EXPIRE_PASSWORD' }
599
+ ($UserFlag -BOR 0x20000) { $List += 'MNS_LOGON_ACCOUNT' }
600
+ ($UserFlag -BOR 0x40000) { $List += 'SMARTCARD_REQUIRED' }
601
+ ($UserFlag -BOR 0x80000) { $List += 'TRUSTED_FOR_DELEGATION' }
602
+ ($UserFlag -BOR 0x100000) { $List += 'NOT_DELEGATED' }
603
+ ($UserFlag -BOR 0x200000) { $List += 'USE_DES_KEY_ONLY' }
604
+ ($UserFlag -BOR 0x400000) { $List += 'DONT_REQ_PREAUTH' }
605
+ ($UserFlag -BOR 0x800000) { $List += 'PASSWORD_EXPIRED' }
606
+ ($UserFlag -BOR 0x1000000) { $List += 'TRUSTED_TO_AUTH_FOR_DELEGATION' }
607
+ ($UserFlag -BOR 0x04000000) { $List += 'PARTIAL_SECRETS_ACCOUNT' }
608
+ }
609
+ $List
610
+ }
611
+
612
+ $Computername = $Env:Computername
613
+ $adsi = [ADSI]"WinNT://$Computername"
614
+ $adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach {
615
+ New-Object PSObject -property @{
616
+ uid = ConvertTo-SID -BinarySID $_.ObjectSID[0]
617
+ username = $_.Name[0]
618
+ description = $_.Description[0]
619
+ disabled = $_.AccountDisabled[0]
620
+ userflags = Convert-UserFlag -UserFlag $_.UserFlags[0]
621
+ passwordage = [math]::Round($_.PasswordAge[0]/86400)
622
+ minpasswordlength = $_.MinPasswordLength[0]
623
+ mindays = [math]::Round($_.MinPasswordAge[0]/86400)
624
+ maxdays = [math]::Round($_.MaxPasswordAge[0]/86400)
625
+ warndays = $null
626
+ badpasswordattempts = $_.BadPasswordAttempts[0]
627
+ maxbadpasswords = $_.MaxBadPasswordsAllowed[0]
628
+ gid = $null
629
+ group = $null
630
+ groups = $null
631
+ home = $_.HomeDirectory[0]
632
+ shell = $null
633
+ domain = $Computername
634
+ }
635
+ } | ConvertTo-Json
595
636
  EOH
596
-
597
637
  cmd = inspec.powershell(script)
598
-
599
638
  # cannot rely on exit code for now, successful command returns exit code 1
600
639
  # return nil if cmd.exit_status != 0, try to parse json
601
640
  begin
602
- params = JSON.parse(cmd.stdout)
641
+ users = JSON.parse(cmd.stdout)
603
642
  rescue JSON::ParserError => _e
604
643
  return nil
605
644
  end
606
645
 
607
- user_hash = params['User'] || {}
608
- group_hashes = params['Groups'] || []
609
- # if groups is no array, generate one
610
- group_hashes = [group_hashes] unless group_hashes.is_a?(Array)
611
- group_names = group_hashes.map { |grp| grp['Caption'] }
612
- {
613
- uid: user_hash['SID'],
614
- username: user_hash['Caption'],
615
- gid: nil,
616
- group: nil,
617
- groups: group_names,
618
- disabled: user_hash['Disabled'],
619
- }
620
- end
621
-
622
- # not implemented yet
623
- def meta_info(_username)
624
- {
625
- home: nil,
626
- shell: nil,
627
- }
628
- end
629
-
630
- def list_users
631
- script = 'Get-WmiObject Win32_UserAccount | Select-Object -ExpandProperty Caption'
632
- cmd = inspec.powershell(script)
633
- cmd.stdout.chomp.lines
646
+ # ensure we have an array of groups
647
+ users = [users] if !users.is_a?(Array)
648
+ # convert keys to symbols
649
+ @users_cache = users.map { |user| user.each_with_object({}) { |(k, v), h| h[k.to_sym] = v } }
634
650
  end
635
651
  end
636
652
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Richter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-25 00:00:00.000000000 Z
11
+ date: 2016-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: train
@@ -525,9 +525,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
525
525
  version: '0'
526
526
  required_rubygems_version: !ruby/object:Gem::Requirement
527
527
  requirements:
528
- - - ">"
528
+ - - ">="
529
529
  - !ruby/object:Gem::Version
530
- version: 1.3.1
530
+ version: '0'
531
531
  requirements: []
532
532
  rubyforge_project:
533
533
  rubygems_version: 2.4.6
@@ -535,3 +535,4 @@ signing_key:
535
535
  specification_version: 4
536
536
  summary: Infrastructure and compliance testing.
537
537
  test_files: []
538
+ has_rdoc: