inspec 0.14.8 → 0.15.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -2
  3. data/bin/inspec +3 -4
  4. data/examples/inheritance/README.md +19 -0
  5. data/examples/inheritance/controls/example.rb +11 -0
  6. data/examples/inheritance/inspec.yml +10 -0
  7. data/lib/bundles/inspec-compliance/cli.rb +1 -4
  8. data/lib/bundles/inspec-supermarket/cli.rb +1 -4
  9. data/lib/inspec/dsl.rb +48 -55
  10. data/lib/inspec/profile.rb +6 -2
  11. data/lib/inspec/profile_context.rb +21 -8
  12. data/lib/inspec/runner.rb +17 -12
  13. data/lib/inspec/runner_rspec.rb +1 -0
  14. data/lib/inspec/version.rb +1 -1
  15. data/lib/resources/apache.rb +20 -18
  16. data/lib/resources/apache_conf.rb +92 -90
  17. data/lib/resources/apt.rb +92 -90
  18. data/lib/resources/audit_policy.rb +35 -33
  19. data/lib/resources/auditd_conf.rb +41 -39
  20. data/lib/resources/auditd_rules.rb +155 -153
  21. data/lib/resources/bond.rb +1 -1
  22. data/lib/resources/bridge.rb +97 -95
  23. data/lib/resources/command.rb +47 -45
  24. data/lib/resources/csv.rb +23 -21
  25. data/lib/resources/directory.rb +1 -1
  26. data/lib/resources/etc_group.rb +116 -114
  27. data/lib/resources/file.rb +1 -1
  28. data/lib/resources/gem.rb +39 -37
  29. data/lib/resources/group.rb +100 -98
  30. data/lib/resources/host.rb +103 -101
  31. data/lib/resources/inetd_conf.rb +42 -40
  32. data/lib/resources/ini.rb +15 -13
  33. data/lib/resources/interface.rb +106 -104
  34. data/lib/resources/iptables.rb +36 -34
  35. data/lib/resources/json.rb +64 -62
  36. data/lib/resources/kernel_module.rb +30 -28
  37. data/lib/resources/kernel_parameter.rb +44 -42
  38. data/lib/resources/limits_conf.rb +41 -39
  39. data/lib/resources/login_def.rb +38 -36
  40. data/lib/resources/mount.rb +43 -41
  41. data/lib/resources/mysql.rb +67 -65
  42. data/lib/resources/mysql_conf.rb +89 -87
  43. data/lib/resources/mysql_session.rb +46 -44
  44. data/lib/resources/npm.rb +35 -33
  45. data/lib/resources/ntp_conf.rb +44 -42
  46. data/lib/resources/oneget.rb +46 -44
  47. data/lib/resources/os.rb +22 -20
  48. data/lib/resources/os_env.rb +47 -45
  49. data/lib/resources/package.rb +213 -211
  50. data/lib/resources/parse_config.rb +59 -57
  51. data/lib/resources/passwd.rb +89 -87
  52. data/lib/resources/pip.rb +60 -58
  53. data/lib/resources/port.rb +352 -350
  54. data/lib/resources/postgres.rb +26 -24
  55. data/lib/resources/postgres_conf.rb +66 -64
  56. data/lib/resources/postgres_session.rb +47 -45
  57. data/lib/resources/processes.rb +56 -54
  58. data/lib/resources/registry_key.rb +150 -148
  59. data/lib/resources/script.rb +30 -28
  60. data/lib/resources/security_policy.rb +56 -54
  61. data/lib/resources/service.rb +638 -636
  62. data/lib/resources/shadow.rb +98 -96
  63. data/lib/resources/ssh_conf.rb +58 -56
  64. data/lib/resources/user.rb +363 -361
  65. data/lib/resources/windows_feature.rb +46 -44
  66. data/lib/resources/xinetd.rb +111 -109
  67. data/lib/resources/yaml.rb +16 -14
  68. data/lib/resources/yum.rb +107 -105
  69. data/lib/utils/base_cli.rb +18 -0
  70. data/test/helper.rb +2 -2
  71. data/test/unit/profile_context_test.rb +1 -1
  72. data/test/unit/resources/file_test.rb +1 -1
  73. data/test/unit/resources/mount_test.rb +1 -1
  74. metadata +5 -2
@@ -15,121 +15,123 @@ require 'forwardable'
15
15
  # - inactive_days before deactivating the account
16
16
  # - expiry_date when this account will expire
17
17
 
18
- class Shadow < Inspec.resource(1)
19
- name 'shadow'
20
- desc 'Use the shadow InSpec resource to test the contents of /etc/shadow, '\
21
- 'which contains the following information for users that may log into '\
22
- 'the system and/or as users that own running processes.'
23
- example "
24
- describe shadow do
25
- its('users') { should_not include 'forbidden_user' }
26
- end
18
+ module Inspec::Resources
19
+ class Shadow < Inspec.resource(1)
20
+ name 'shadow'
21
+ desc 'Use the shadow InSpec resource to test the contents of /etc/shadow, '\
22
+ 'which contains the following information for users that may log into '\
23
+ 'the system and/or as users that own running processes.'
24
+ example "
25
+ describe shadow do
26
+ its('users') { should_not include 'forbidden_user' }
27
+ end
27
28
 
28
- describe shadow.users('bin') do
29
- its('password') { should cmp 'x' }
30
- its('count') { should eq 1 }
29
+ describe shadow.users('bin') do
30
+ its('password') { should cmp 'x' }
31
+ its('count') { should eq 1 }
32
+ end
33
+ "
34
+
35
+ extend Forwardable
36
+ attr_reader :params
37
+ attr_reader :content
38
+ attr_reader :lines
39
+
40
+ def initialize(path = '/etc/shadow', opts = nil)
41
+ opts ||= {}
42
+ @path = path || '/etc/shadow'
43
+ @content = opts[:content] || inspec.file(@path).content
44
+ @lines = @content.to_s.split("\n")
45
+ @filters = opts[:filters] || ''
46
+ @params = @lines.map { |l| parse_shadow_line(l) }
31
47
  end
32
- "
33
-
34
- extend Forwardable
35
- attr_reader :params
36
- attr_reader :content
37
- attr_reader :lines
38
-
39
- def initialize(path = '/etc/shadow', opts = nil)
40
- opts ||= {}
41
- @path = path || '/etc/shadow'
42
- @content = opts[:content] || inspec.file(@path).content
43
- @lines = @content.to_s.split("\n")
44
- @filters = opts[:filters] || ''
45
- @params = @lines.map { |l| parse_shadow_line(l) }
46
- end
47
48
 
48
- def filter(hm = {})
49
- return self if hm.nil? || hm.empty?
50
- res = @params
51
- filters = ''
52
- hm.each do |attr, condition|
53
- condition = condition.to_s if condition.is_a? Integer
54
- filters += " #{attr} = #{condition.inspect}"
55
- res = res.find_all do |line|
56
- case line[attr.to_s]
57
- when condition
58
- true
59
- else
60
- false
49
+ def filter(hm = {})
50
+ return self if hm.nil? || hm.empty?
51
+ res = @params
52
+ filters = ''
53
+ hm.each do |attr, condition|
54
+ condition = condition.to_s if condition.is_a? Integer
55
+ filters += " #{attr} = #{condition.inspect}"
56
+ res = res.find_all do |line|
57
+ case line[attr.to_s]
58
+ when condition
59
+ true
60
+ else
61
+ false
62
+ end
61
63
  end
62
64
  end
65
+ content = res.map { |x| x.values.join(':') }.join("\n")
66
+ Shadow.new(@path, content: content, filters: @filters + filters)
63
67
  end
64
- content = res.map { |x| x.values.join(':') }.join("\n")
65
- Shadow.new(@path, content: content, filters: @filters + filters)
66
- end
67
68
 
68
- def entries
69
- @lines.map { |line| Shadow.new(@path, content: line, filters: @filters) }
70
- end
69
+ def entries
70
+ @lines.map { |line| Shadow.new(@path, content: line, filters: @filters) }
71
+ end
71
72
 
72
- def users(name = nil)
73
- name.nil? ? map_data('user') : filter(user: name)
74
- end
73
+ def users(name = nil)
74
+ name.nil? ? map_data('user') : filter(user: name)
75
+ end
75
76
 
76
- def passwords(password = nil)
77
- password.nil? ? map_data('password') : filter(password: password)
78
- end
77
+ def passwords(password = nil)
78
+ password.nil? ? map_data('password') : filter(password: password)
79
+ end
79
80
 
80
- def last_changes(filter_by = nil)
81
- filter_by.nil? ? map_data('last_change') : filter(last_change: filter_by)
82
- end
81
+ def last_changes(filter_by = nil)
82
+ filter_by.nil? ? map_data('last_change') : filter(last_change: filter_by)
83
+ end
83
84
 
84
- def min_days(filter_by = nil)
85
- filter_by.nil? ? map_data('min_days') : filter(min_days: filter_by)
86
- end
85
+ def min_days(filter_by = nil)
86
+ filter_by.nil? ? map_data('min_days') : filter(min_days: filter_by)
87
+ end
87
88
 
88
- def max_days(filter_by = nil)
89
- filter_by.nil? ? map_data('max_days') : filter(max_days: filter_by)
90
- end
89
+ def max_days(filter_by = nil)
90
+ filter_by.nil? ? map_data('max_days') : filter(max_days: filter_by)
91
+ end
91
92
 
92
- def warn_days(filter_by = nil)
93
- filter_by.nil? ? map_data('warn_days') : filter(warn_days: filter_by)
94
- end
93
+ def warn_days(filter_by = nil)
94
+ filter_by.nil? ? map_data('warn_days') : filter(warn_days: filter_by)
95
+ end
95
96
 
96
- def inactive_days(filter_by = nil)
97
- filter_by.nil? ? map_data('inactive_days') : filter(inactive_days: filter_by)
98
- end
97
+ def inactive_days(filter_by = nil)
98
+ filter_by.nil? ? map_data('inactive_days') : filter(inactive_days: filter_by)
99
+ end
99
100
 
100
- def expiry_dates(filter_by = nil)
101
- filter_by.nil? ? map_data('expiry_date') : filter(expiry_date: filter_by)
102
- end
101
+ def expiry_dates(filter_by = nil)
102
+ filter_by.nil? ? map_data('expiry_date') : filter(expiry_date: filter_by)
103
+ end
103
104
 
104
- def to_s
105
- f = @filters.empty? ? '' : ' with'+@filters
106
- "/etc/shadow#{f}"
107
- end
105
+ def to_s
106
+ f = @filters.empty? ? '' : ' with'+@filters
107
+ "/etc/shadow#{f}"
108
+ end
108
109
 
109
- def_delegator :@params, :length, :count
110
+ def_delegator :@params, :length, :count
110
111
 
111
- private
112
+ private
112
113
 
113
- def map_data(id)
114
- @params.map { |x| x[id] }
115
- end
114
+ def map_data(id)
115
+ @params.map { |x| x[id] }
116
+ end
116
117
 
117
- # Parse a line of /etc/shadow
118
- #
119
- # @param [String] line a line of /etc/shadow
120
- # @return [Hash] Map of entries in this line
121
- def parse_shadow_line(line)
122
- x = line.split(':')
123
- {
124
- 'user' => x.at(0),
125
- 'password' => x.at(1),
126
- 'last_change' => x.at(2),
127
- 'min_days' => x.at(3),
128
- 'max_days' => x.at(4),
129
- 'warn_days' => x.at(5),
130
- 'inactive_days' => x.at(6),
131
- 'expiry_date' => x.at(7),
132
- 'reserved' => x.at(8),
133
- }
118
+ # Parse a line of /etc/shadow
119
+ #
120
+ # @param [String] line a line of /etc/shadow
121
+ # @return [Hash] Map of entries in this line
122
+ def parse_shadow_line(line)
123
+ x = line.split(':')
124
+ {
125
+ 'user' => x.at(0),
126
+ 'password' => x.at(1),
127
+ 'last_change' => x.at(2),
128
+ 'min_days' => x.at(3),
129
+ 'max_days' => x.at(4),
130
+ 'warn_days' => x.at(5),
131
+ 'inactive_days' => x.at(6),
132
+ 'expiry_date' => x.at(7),
133
+ 'reserved' => x.at(8),
134
+ }
135
+ end
134
136
  end
135
137
  end
@@ -6,76 +6,78 @@
6
6
 
7
7
  require 'utils/simpleconfig'
8
8
 
9
- class SshConf < Inspec.resource(1)
10
- name 'ssh_config'
11
- desc 'Use the sshd_config InSpec audit resource to test configuration data for the Open SSH daemon located at /etc/ssh/sshd_config on Linux and UNIX platforms. sshd---the Open SSH daemon---listens on dedicated ports, starts a daemon for each incoming connection, and then handles encryption, authentication, key exchanges, command executation, and data exchanges.'
12
- example "
13
- describe sshd_config do
14
- its('Protocol') { should eq '2' }
9
+ module Inspec::Resources
10
+ class SshConf < Inspec.resource(1)
11
+ name 'ssh_config'
12
+ desc 'Use the sshd_config InSpec audit resource to test configuration data for the Open SSH daemon located at /etc/ssh/sshd_config on Linux and UNIX platforms. sshd---the Open SSH daemon---listens on dedicated ports, starts a daemon for each incoming connection, and then handles encryption, authentication, key exchanges, command executation, and data exchanges.'
13
+ example "
14
+ describe sshd_config do
15
+ its('Protocol') { should eq '2' }
16
+ end
17
+ "
18
+
19
+ def initialize(conf_path = nil, type = nil)
20
+ @conf_path = conf_path || '/etc/ssh/ssh_config'
21
+ typename = (@conf_path.include?('sshd') ? 'Server' : 'Client')
22
+ @type = type || "SSH #{typename} configuration #{conf_path}"
15
23
  end
16
- "
17
24
 
18
- def initialize(conf_path = nil, type = nil)
19
- @conf_path = conf_path || '/etc/ssh/ssh_config'
20
- typename = (@conf_path.include?('sshd') ? 'Server' : 'Client')
21
- @type = type || "SSH #{typename} configuration #{conf_path}"
22
- end
25
+ def content
26
+ read_content
27
+ end
23
28
 
24
- def content
25
- read_content
26
- end
29
+ def params(*opts)
30
+ opts.inject(read_params) do |res, nxt|
31
+ res.respond_to?(:key) ? res[nxt] : nil
32
+ end
33
+ end
27
34
 
28
- def params(*opts)
29
- opts.inject(read_params) do |res, nxt|
30
- res.respond_to?(:key) ? res[nxt] : nil
35
+ def method_missing(name)
36
+ param = read_params[name.to_s]
37
+ return nil if param.nil?
38
+ # extract first value if we have only one value in array
39
+ return param[0] if param.length == 1
40
+ param
31
41
  end
32
- end
33
42
 
34
- def method_missing(name)
35
- param = read_params[name.to_s]
36
- return nil if param.nil?
37
- # extract first value if we have only one value in array
38
- return param[0] if param.length == 1
39
- param
40
- end
43
+ def to_s
44
+ 'SSH Configuration'
45
+ end
41
46
 
42
- def to_s
43
- 'SSH Configuration'
44
- end
47
+ private
45
48
 
46
- private
49
+ def read_content
50
+ return @content if defined?(@content)
51
+ file = inspec.file(@conf_path)
52
+ if !file.file?
53
+ return skip_resource "Can't find file \"#{@conf_path}\""
54
+ end
47
55
 
48
- def read_content
49
- return @content if defined?(@content)
50
- file = inspec.file(@conf_path)
51
- if !file.file?
52
- return skip_resource "Can't find file \"#{@conf_path}\""
53
- end
56
+ @content = file.content
57
+ if @content.empty? && file.size > 0
58
+ return skip_resource "Can't read file \"#{@conf_path}\""
59
+ end
54
60
 
55
- @content = file.content
56
- if @content.empty? && file.size > 0
57
- return skip_resource "Can't read file \"#{@conf_path}\""
61
+ @content
58
62
  end
59
63
 
60
- @content
61
- end
62
-
63
- def read_params
64
- return @params if defined?(@params)
65
- return @params = {} if read_content.nil?
66
- conf = SimpleConfig.new(
67
- read_content,
68
- assignment_re: /^\s*(\S+?)\s+(.*?)\s*$/,
69
- multiple_values: true,
70
- )
71
- @params = conf.params
64
+ def read_params
65
+ return @params if defined?(@params)
66
+ return @params = {} if read_content.nil?
67
+ conf = SimpleConfig.new(
68
+ read_content,
69
+ assignment_re: /^\s*(\S+?)\s+(.*?)\s*$/,
70
+ multiple_values: true,
71
+ )
72
+ @params = conf.params
73
+ end
72
74
  end
73
- end
74
75
 
75
- class SshdConf < SshConf
76
- name 'sshd_config'
76
+ class SshdConf < SshConf
77
+ name 'sshd_config'
77
78
 
78
- def initialize(path = nil)
79
- super(path || '/etc/ssh/sshd_config')
79
+ def initialize(path = nil)
80
+ super(path || '/etc/ssh/sshd_config')
81
+ end
80
82
  end
81
83
  end
@@ -38,421 +38,423 @@
38
38
  require 'utils/parser'
39
39
  require 'utils/convert'
40
40
 
41
- class User < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
42
- name 'user'
43
- desc 'Use the user InSpec audit resource to test user profiles, including the groups to which they belong, the frequency of required password changes, the directory paths to home and shell.'
44
- example "
45
- describe user('root') do
46
- it { should exist }
47
- its('uid') { should eq 1234 }
48
- its('gid') { should eq 1234 }
49
- end
50
- "
51
- def initialize(user)
52
- @user = user
53
-
54
- # select package manager
55
- @user_provider = nil
56
- os = inspec.os
57
- if os.linux?
58
- @user_provider = LinuxUser.new(inspec)
59
- elsif os.windows?
60
- @user_provider = WindowsUser.new(inspec)
61
- elsif ['darwin'].include?(os[:family])
62
- @user_provider = DarwinUser.new(inspec)
63
- elsif ['freebsd'].include?(os[:family])
64
- @user_provider = FreeBSDUser.new(inspec)
65
- elsif ['aix'].include?(os[:family])
66
- @user_provider = AixUser.new(inspec)
67
- elsif os.solaris?
68
- @user_provider = SolarisUser.new(inspec)
69
- else
70
- return skip_resource 'The `user` resource is not supported on your OS yet.'
41
+ module Inspec::Resources
42
+ class User < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
43
+ name 'user'
44
+ desc 'Use the user InSpec audit resource to test user profiles, including the groups to which they belong, the frequency of required password changes, the directory paths to home and shell.'
45
+ example "
46
+ describe user('root') do
47
+ it { should exist }
48
+ its('uid') { should eq 1234 }
49
+ its('gid') { should eq 1234 }
50
+ end
51
+ "
52
+ def initialize(user)
53
+ @user = user
54
+
55
+ # select package manager
56
+ @user_provider = nil
57
+ os = inspec.os
58
+ if os.linux?
59
+ @user_provider = LinuxUser.new(inspec)
60
+ elsif os.windows?
61
+ @user_provider = WindowsUser.new(inspec)
62
+ elsif ['darwin'].include?(os[:family])
63
+ @user_provider = DarwinUser.new(inspec)
64
+ elsif ['freebsd'].include?(os[:family])
65
+ @user_provider = FreeBSDUser.new(inspec)
66
+ elsif ['aix'].include?(os[:family])
67
+ @user_provider = AixUser.new(inspec)
68
+ elsif os.solaris?
69
+ @user_provider = SolarisUser.new(inspec)
70
+ else
71
+ return skip_resource 'The `user` resource is not supported on your OS yet.'
72
+ end
71
73
  end
72
- end
73
74
 
74
- def exists?
75
- !identity.nil? && !identity[:user].nil?
76
- end
75
+ def exists?
76
+ !identity.nil? && !identity[:user].nil?
77
+ end
77
78
 
78
- def uid
79
- identity[:uid] unless identity.nil?
80
- end
79
+ def uid
80
+ identity[:uid] unless identity.nil?
81
+ end
81
82
 
82
- def gid
83
- identity[:gid] unless identity.nil?
84
- end
83
+ def gid
84
+ identity[:gid] unless identity.nil?
85
+ end
85
86
 
86
- def group
87
- identity[:group] unless identity.nil?
88
- end
87
+ def group
88
+ identity[:group] unless identity.nil?
89
+ end
89
90
 
90
- def groups
91
- identity[:groups] unless identity.nil?
92
- end
91
+ def groups
92
+ identity[:groups] unless identity.nil?
93
+ end
93
94
 
94
- def home
95
- meta_info[:home] unless meta_info.nil?
96
- end
95
+ def home
96
+ meta_info[:home] unless meta_info.nil?
97
+ end
97
98
 
98
- def shell
99
- meta_info[:shell] unless meta_info.nil?
100
- end
99
+ def shell
100
+ meta_info[:shell] unless meta_info.nil?
101
+ end
101
102
 
102
- # returns the minimum days between password changes
103
- def mindays
104
- credentials[:mindays] unless credentials.nil?
105
- end
103
+ # returns the minimum days between password changes
104
+ def mindays
105
+ credentials[:mindays] unless credentials.nil?
106
+ end
106
107
 
107
- # returns the maximum days between password changes
108
- def maxdays
109
- credentials[:maxdays] unless credentials.nil?
110
- end
108
+ # returns the maximum days between password changes
109
+ def maxdays
110
+ credentials[:maxdays] unless credentials.nil?
111
+ end
111
112
 
112
- # returns the days for password change warning
113
- def warndays
114
- credentials[:warndays] unless credentials.nil?
115
- end
113
+ # returns the days for password change warning
114
+ def warndays
115
+ credentials[:warndays] unless credentials.nil?
116
+ end
116
117
 
117
- # implement 'mindays' method to be compatible with serverspec
118
- def minimum_days_between_password_change
119
- deprecated('minimum_days_between_password_change', "Please use 'its(:mindays)'")
120
- mindays
121
- end
118
+ # implement 'mindays' method to be compatible with serverspec
119
+ def minimum_days_between_password_change
120
+ deprecated('minimum_days_between_password_change', "Please use 'its(:mindays)'")
121
+ mindays
122
+ end
122
123
 
123
- # implement 'maxdays' method to be compatible with serverspec
124
- def maximum_days_between_password_change
125
- deprecated('maximum_days_between_password_change', "Please use 'its(:maxdays)'")
126
- maxdays
127
- end
124
+ # implement 'maxdays' method to be compatible with serverspec
125
+ def maximum_days_between_password_change
126
+ deprecated('maximum_days_between_password_change', "Please use 'its(:maxdays)'")
127
+ maxdays
128
+ end
128
129
 
129
- # implements rspec has matcher, to be compatible with serverspec
130
- # @see: https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers/built_in/has.rb
131
- def has_uid?(compare_uid)
132
- deprecated('has_uid?')
133
- uid == compare_uid
134
- end
130
+ # implements rspec has matcher, to be compatible with serverspec
131
+ # @see: https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers/built_in/has.rb
132
+ def has_uid?(compare_uid)
133
+ deprecated('has_uid?')
134
+ uid == compare_uid
135
+ end
135
136
 
136
- def has_home_directory?(compare_home)
137
- deprecated('has_home_directory?', "Please use 'its(:home)'")
138
- home == compare_home
139
- end
137
+ def has_home_directory?(compare_home)
138
+ deprecated('has_home_directory?', "Please use 'its(:home)'")
139
+ home == compare_home
140
+ end
140
141
 
141
- def has_login_shell?(compare_shell)
142
- deprecated('has_login_shell?', "Please use 'its(:shell)'")
143
- shell == compare_shell
144
- end
142
+ def has_login_shell?(compare_shell)
143
+ deprecated('has_login_shell?', "Please use 'its(:shell)'")
144
+ shell == compare_shell
145
+ end
145
146
 
146
- def has_authorized_key?(_compare_key)
147
- deprecated('has_authorized_key?')
148
- fail NotImplementedError
149
- end
147
+ def has_authorized_key?(_compare_key)
148
+ deprecated('has_authorized_key?')
149
+ fail NotImplementedError
150
+ end
150
151
 
151
- def deprecated(name, alternative = nil)
152
- warn "[DEPRECATION] #{name} is deprecated. #{alternative}"
153
- end
152
+ def deprecated(name, alternative = nil)
153
+ warn "[DEPRECATION] #{name} is deprecated. #{alternative}"
154
+ end
154
155
 
155
- def to_s
156
- "User #{@user}"
157
- end
156
+ def to_s
157
+ "User #{@user}"
158
+ end
158
159
 
159
- def identity
160
- return @id_cache if defined?(@id_cache)
161
- @id_cache = @user_provider.identity(@user) if !@user_provider.nil?
162
- end
160
+ def identity
161
+ return @id_cache if defined?(@id_cache)
162
+ @id_cache = @user_provider.identity(@user) if !@user_provider.nil?
163
+ end
163
164
 
164
- private
165
+ private
165
166
 
166
- def meta_info
167
- return @meta_cache if defined?(@meta_cache)
168
- @meta_cache = @user_provider.meta_info(@user) if !@user_provider.nil?
169
- end
167
+ def meta_info
168
+ return @meta_cache if defined?(@meta_cache)
169
+ @meta_cache = @user_provider.meta_info(@user) if !@user_provider.nil?
170
+ end
170
171
 
171
- def credentials
172
- return @cred_cache if defined?(@cred_cache)
173
- @cred_cache = @user_provider.credentials(@user) if !@user_provider.nil?
172
+ def credentials
173
+ return @cred_cache if defined?(@cred_cache)
174
+ @cred_cache = @user_provider.credentials(@user) if !@user_provider.nil?
175
+ end
174
176
  end
175
- end
176
177
 
177
- class UserInfo
178
- include Converter
179
-
180
- attr_reader :inspec
181
- def initialize(inspec)
182
- @inspec = inspec
183
- end
178
+ class UserInfo
179
+ include Converter
184
180
 
185
- def credentials(_username)
186
- end
187
- end
181
+ attr_reader :inspec
182
+ def initialize(inspec)
183
+ @inspec = inspec
184
+ end
188
185
 
189
- # implements generic unix id handling
190
- class UnixUser < UserInfo
191
- attr_reader :inspec, :id_cmd
192
- def initialize(inspec)
193
- @inspec = inspec
194
- @id_cmd ||= 'id'
195
- super
186
+ def credentials(_username)
187
+ end
196
188
  end
197
189
 
198
- # parse one id entry like '0(wheel)''
199
- def parse_value(line)
200
- SimpleConfig.new(
201
- line,
202
- line_separator: ',',
203
- assignment_re: /^\s*([^\(]*?)\s*\(\s*(.*?)\)*$/,
204
- group_re: nil,
205
- multiple_values: false,
206
- ).params
207
- end
190
+ # implements generic unix id handling
191
+ class UnixUser < UserInfo
192
+ attr_reader :inspec, :id_cmd
193
+ def initialize(inspec)
194
+ @inspec = inspec
195
+ @id_cmd ||= 'id'
196
+ super
197
+ end
208
198
 
209
- # extracts the identity
210
- def identity(username)
211
- cmd = inspec.command("#{id_cmd} #{username}")
212
- return nil if cmd.exit_status != 0
213
-
214
- # parse words
215
- params = SimpleConfig.new(
216
- parse_id_entries(cmd.stdout.chomp),
217
- assignment_re: /^\s*([^=]*?)\s*=\s*(.*?)\s*$/,
218
- group_re: nil,
219
- multiple_values: false,
220
- ).params
221
-
222
- {
223
- uid: convert_to_i(parse_value(params['uid']).keys[0]),
224
- user: parse_value(params['uid']).values[0],
225
- gid: convert_to_i(parse_value(params['gid']).keys[0]),
226
- group: parse_value(params['gid']).values[0],
227
- groups: parse_value(params['groups']).values,
228
- }
229
- end
199
+ # parse one id entry like '0(wheel)''
200
+ def parse_value(line)
201
+ SimpleConfig.new(
202
+ line,
203
+ line_separator: ',',
204
+ assignment_re: /^\s*([^\(]*?)\s*\(\s*(.*?)\)*$/,
205
+ group_re: nil,
206
+ multiple_values: false,
207
+ ).params
208
+ end
230
209
 
231
- # splits the results of id into seperate lines
232
- def parse_id_entries(raw)
233
- data = []
234
- until (index = raw.index(/\)\s{1}/)).nil?
235
- data.push(raw[0, index+1]) # inclue closing )
236
- raw = raw[index+2, raw.length-index-2]
210
+ # extracts the identity
211
+ def identity(username)
212
+ cmd = inspec.command("#{id_cmd} #{username}")
213
+ return nil if cmd.exit_status != 0
214
+
215
+ # parse words
216
+ params = SimpleConfig.new(
217
+ parse_id_entries(cmd.stdout.chomp),
218
+ assignment_re: /^\s*([^=]*?)\s*=\s*(.*?)\s*$/,
219
+ group_re: nil,
220
+ multiple_values: false,
221
+ ).params
222
+
223
+ {
224
+ uid: convert_to_i(parse_value(params['uid']).keys[0]),
225
+ user: parse_value(params['uid']).values[0],
226
+ gid: convert_to_i(parse_value(params['gid']).keys[0]),
227
+ group: parse_value(params['gid']).values[0],
228
+ groups: parse_value(params['groups']).values,
229
+ }
237
230
  end
238
- data.push(raw) if !raw.nil?
239
- data.join("\n")
240
- end
241
- end
242
231
 
243
- class LinuxUser < UnixUser
244
- include PasswdParser
245
- include CommentParser
246
-
247
- def meta_info(username)
248
- cmd = inspec.command("getent passwd #{username}")
249
- return nil if cmd.exit_status != 0
250
- # returns: root:x:0:0:root:/root:/bin/bash
251
- passwd = parse_passwd_line(cmd.stdout.chomp)
252
- {
253
- home: passwd['home'],
254
- shell: passwd['shell'],
255
- }
232
+ # splits the results of id into seperate lines
233
+ def parse_id_entries(raw)
234
+ data = []
235
+ until (index = raw.index(/\)\s{1}/)).nil?
236
+ data.push(raw[0, index+1]) # inclue closing )
237
+ raw = raw[index+2, raw.length-index-2]
238
+ end
239
+ data.push(raw) if !raw.nil?
240
+ data.join("\n")
241
+ end
256
242
  end
257
243
 
258
- def credentials(username)
259
- cmd = inspec.command("chage -l #{username}")
260
- return nil if cmd.exit_status != 0
261
-
262
- params = SimpleConfig.new(
263
- cmd.stdout.chomp,
264
- assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
265
- group_re: nil,
266
- multiple_values: false,
267
- ).params
268
-
269
- {
270
- mindays: convert_to_i(params['Minimum number of days between password change']),
271
- maxdays: convert_to_i(params['Maximum number of days between password change']),
272
- warndays: convert_to_i(params['Number of days of warning before password expires']),
273
- }
274
- end
275
- end
244
+ class LinuxUser < UnixUser
245
+ include PasswdParser
246
+ include CommentParser
276
247
 
277
- class SolarisUser < LinuxUser
278
- def initialize(inspec)
279
- @inspec = inspec
280
- @id_cmd ||= 'id -a'
281
- super
282
- end
248
+ def meta_info(username)
249
+ cmd = inspec.command("getent passwd #{username}")
250
+ return nil if cmd.exit_status != 0
251
+ # returns: root:x:0:0:root:/root:/bin/bash
252
+ passwd = parse_passwd_line(cmd.stdout.chomp)
253
+ {
254
+ home: passwd['home'],
255
+ shell: passwd['shell'],
256
+ }
257
+ end
283
258
 
284
- def credentials(_username)
285
- nil
259
+ def credentials(username)
260
+ cmd = inspec.command("chage -l #{username}")
261
+ return nil if cmd.exit_status != 0
262
+
263
+ params = SimpleConfig.new(
264
+ cmd.stdout.chomp,
265
+ assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
266
+ group_re: nil,
267
+ multiple_values: false,
268
+ ).params
269
+
270
+ {
271
+ mindays: convert_to_i(params['Minimum number of days between password change']),
272
+ maxdays: convert_to_i(params['Maximum number of days between password change']),
273
+ warndays: convert_to_i(params['Number of days of warning before password expires']),
274
+ }
275
+ end
286
276
  end
287
- end
288
277
 
289
- class AixUser < UnixUser
290
- def identity(username)
291
- id = super(username)
292
- return nil if id.nil?
293
- # AIX 'id' command doesn't include the primary group in the supplementary
294
- # yet it can be somewhere in the supplementary list if someone added root
295
- # to a groups list in /etc/group
296
- # we rearrange to expected list if that is the case
297
- if id[:groups].first != id[:group]
298
- id[:groups].reject! { |i| i == id[:group] } if id[:groups].include?(id[:group])
299
- id[:groups].unshift(id[:group])
278
+ class SolarisUser < LinuxUser
279
+ def initialize(inspec)
280
+ @inspec = inspec
281
+ @id_cmd ||= 'id -a'
282
+ super
300
283
  end
301
284
 
302
- id
285
+ def credentials(_username)
286
+ nil
287
+ end
303
288
  end
304
289
 
305
- def meta_info(username)
306
- lsuser = inspec.command("lsuser -C -a home shell #{username}")
307
- return nil if lsuser.exit_status != 0
290
+ class AixUser < UnixUser
291
+ def identity(username)
292
+ id = super(username)
293
+ return nil if id.nil?
294
+ # AIX 'id' command doesn't include the primary group in the supplementary
295
+ # yet it can be somewhere in the supplementary list if someone added root
296
+ # to a groups list in /etc/group
297
+ # we rearrange to expected list if that is the case
298
+ if id[:groups].first != id[:group]
299
+ id[:groups].reject! { |i| i == id[:group] } if id[:groups].include?(id[:group])
300
+ id[:groups].unshift(id[:group])
301
+ end
302
+
303
+ id
304
+ end
308
305
 
309
- user = lsuser.stdout.chomp.split("\n").last.split(':')
310
- {
311
- home: user[1],
312
- shell: user[2],
313
- }
314
- end
306
+ def meta_info(username)
307
+ lsuser = inspec.command("lsuser -C -a home shell #{username}")
308
+ return nil if lsuser.exit_status != 0
315
309
 
316
- def credentials(username)
317
- cmd = inspec.command(
318
- "lssec -c -f /etc/security/user -s #{username} -a minage -a maxage -a pwdwarntime",
319
- )
320
- return nil if cmd.exit_status != 0
310
+ user = lsuser.stdout.chomp.split("\n").last.split(':')
311
+ {
312
+ home: user[1],
313
+ shell: user[2],
314
+ }
315
+ end
321
316
 
322
- user_sec = cmd.stdout.chomp.split("\n").last.split(':')
317
+ def credentials(username)
318
+ cmd = inspec.command(
319
+ "lssec -c -f /etc/security/user -s #{username} -a minage -a maxage -a pwdwarntime",
320
+ )
321
+ return nil if cmd.exit_status != 0
323
322
 
324
- {
325
- mindays: user_sec[1].to_i * 7,
326
- maxdays: user_sec[2].to_i * 7,
327
- warndays: user_sec[3].to_i,
328
- }
329
- end
330
- end
323
+ user_sec = cmd.stdout.chomp.split("\n").last.split(':')
331
324
 
332
- # we do not use 'finger' for MacOS, because it is harder to parse data with it
333
- # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/fingerd.8.html
334
- # instead we use 'dscl' to request user data
335
- # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dscl.1.html
336
- # @see http://superuser.com/questions/592921/mac-osx-users-vs-dscl-command-to-list-user
337
- class DarwinUser < UnixUser
338
- def meta_info(username)
339
- cmd = inspec.command("dscl -q . -read /Users/#{username} NFSHomeDirectory PrimaryGroupID RecordName UniqueID UserShell")
340
- return nil if cmd.exit_status != 0
341
-
342
- params = SimpleConfig.new(
343
- cmd.stdout.chomp,
344
- assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
345
- group_re: nil,
346
- multiple_values: false,
347
- ).params
348
-
349
- {
350
- home: params['NFSHomeDirectory'],
351
- shell: params['UserShell'],
352
- }
325
+ {
326
+ mindays: user_sec[1].to_i * 7,
327
+ maxdays: user_sec[2].to_i * 7,
328
+ warndays: user_sec[3].to_i,
329
+ }
330
+ end
353
331
  end
354
- end
355
332
 
356
- # FreeBSD recommends to use the 'pw' command for user management
357
- # @see: https://www.freebsd.org/doc/handbook/users-synopsis.html
358
- # @see: https://www.freebsd.org/cgi/man.cgi?pw(8)
359
- # It offers the following commands:
360
- # - adduser(8) The recommended command-line application for adding new users.
361
- # - rmuser(8) The recommended command-line application for removing users.
362
- # - chpass(1) A flexible tool for changing user database information.
363
- # - passwd(1) The command-line tool to change user passwords.
364
- class FreeBSDUser < UnixUser
365
- include PasswdParser
366
-
367
- def meta_info(username)
368
- cmd = inspec.command("pw usershow #{username} -7")
369
- return nil if cmd.exit_status != 0
370
- # returns: root:*:0:0:Charlie &:/root:/bin/csh
371
- passwd = parse_passwd_line(cmd.stdout.chomp)
372
- {
373
- home: passwd['home'],
374
- shell: passwd['shell'],
375
- }
333
+ # we do not use 'finger' for MacOS, because it is harder to parse data with it
334
+ # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/fingerd.8.html
335
+ # instead we use 'dscl' to request user data
336
+ # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dscl.1.html
337
+ # @see http://superuser.com/questions/592921/mac-osx-users-vs-dscl-command-to-list-user
338
+ class DarwinUser < UnixUser
339
+ def meta_info(username)
340
+ cmd = inspec.command("dscl -q . -read /Users/#{username} NFSHomeDirectory PrimaryGroupID RecordName UniqueID UserShell")
341
+ return nil if cmd.exit_status != 0
342
+
343
+ params = SimpleConfig.new(
344
+ cmd.stdout.chomp,
345
+ assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
346
+ group_re: nil,
347
+ multiple_values: false,
348
+ ).params
349
+
350
+ {
351
+ home: params['NFSHomeDirectory'],
352
+ shell: params['UserShell'],
353
+ }
354
+ end
376
355
  end
377
- end
378
356
 
379
- # For now, we stick with WMI Win32_UserAccount
380
- # @see https://msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx
381
- # @see https://msdn.microsoft.com/en-us/library/aa394153(v=vs.85).aspx
382
- #
383
- # using Get-AdUser would be the best command for domain machines, but it will not be installed
384
- # on client machines by default
385
- # @see https://technet.microsoft.com/en-us/library/ee617241.aspx
386
- # @see https://technet.microsoft.com/en-us/library/hh509016(v=WS.10).aspx
387
- # @see http://woshub.com/get-aduser-getting-active-directory-users-data-via-powershell/
388
- # @see http://stackoverflow.com/questions/17548523/the-term-get-aduser-is-not-recognized-as-the-name-of-a-cmdlet
389
- #
390
- # Just for reference, we could also use ADSI (Active Directory Service Interfaces)
391
- # @see https://mcpmag.com/articles/2015/04/15/reporting-on-local-accounts.aspx
392
- class WindowsUser < UserInfo
393
- # parse windows account name
394
- def parse_windows_account(username)
395
- account = username.split('\\')
396
- name = account.pop
397
- domain = account.pop if account.size > 0
398
- [name, domain]
357
+ # FreeBSD recommends to use the 'pw' command for user management
358
+ # @see: https://www.freebsd.org/doc/handbook/users-synopsis.html
359
+ # @see: https://www.freebsd.org/cgi/man.cgi?pw(8)
360
+ # It offers the following commands:
361
+ # - adduser(8) The recommended command-line application for adding new users.
362
+ # - rmuser(8) The recommended command-line application for removing users.
363
+ # - chpass(1) A flexible tool for changing user database information.
364
+ # - passwd(1) The command-line tool to change user passwords.
365
+ class FreeBSDUser < UnixUser
366
+ include PasswdParser
367
+
368
+ def meta_info(username)
369
+ cmd = inspec.command("pw usershow #{username} -7")
370
+ return nil if cmd.exit_status != 0
371
+ # returns: root:*:0:0:Charlie &:/root:/bin/csh
372
+ passwd = parse_passwd_line(cmd.stdout.chomp)
373
+ {
374
+ home: passwd['home'],
375
+ shell: passwd['shell'],
376
+ }
377
+ end
399
378
  end
400
379
 
401
- def identity(username)
402
- # extract domain/user information
403
- account, domain = parse_windows_account(username)
404
-
405
- # TODO: escape content
406
- if !domain.nil?
407
- filter = "Name = '#{account}' and Domain = '#{domain}'"
408
- else
409
- filter = "Name = '#{account}' and LocalAccount = true"
410
- end
411
-
412
- script = <<-EOH
413
- # find user
414
- $user = Get-WmiObject Win32_UserAccount -filter "#{filter}"
415
- # get related groups
416
- $groups = $user.GetRelated('Win32_Group') | Select-Object -Property Caption, Domain, Name, LocalAccount, SID, SIDType, Status
417
- # filter user information
418
- $user = $user | Select-Object -Property Caption, Description, Domain, Name, LocalAccount, Lockout, PasswordChangeable, PasswordExpires, PasswordRequired, SID, SIDType, Status
419
- # build response object
420
- New-Object -Type PSObject | `
421
- Add-Member -MemberType NoteProperty -Name User -Value ($user) -PassThru | `
422
- Add-Member -MemberType NoteProperty -Name Groups -Value ($groups) -PassThru | `
423
- ConvertTo-Json
424
- EOH
425
-
426
- cmd = inspec.script(script)
427
-
428
- # cannot rely on exit code for now, successful command returns exit code 1
429
- # return nil if cmd.exit_status != 0, try to parse json
430
- begin
431
- params = JSON.parse(cmd.stdout)
432
- rescue JSON::ParserError => _e
433
- return nil
434
- end
435
-
436
- user = params['User']['Caption'] unless params['User'].nil?
437
- groups = params['Groups']
438
- # if groups is no array, generate one
439
- groups = [groups] if !groups.is_a?(Array)
440
- groups = groups.map { |grp| grp['Caption'] } unless params['Groups'].nil?
441
-
442
- {
443
- uid: nil,
444
- user: user,
445
- gid: nil,
446
- group: nil,
447
- groups: groups,
448
- }
449
- end
380
+ # For now, we stick with WMI Win32_UserAccount
381
+ # @see https://msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx
382
+ # @see https://msdn.microsoft.com/en-us/library/aa394153(v=vs.85).aspx
383
+ #
384
+ # using Get-AdUser would be the best command for domain machines, but it will not be installed
385
+ # on client machines by default
386
+ # @see https://technet.microsoft.com/en-us/library/ee617241.aspx
387
+ # @see https://technet.microsoft.com/en-us/library/hh509016(v=WS.10).aspx
388
+ # @see http://woshub.com/get-aduser-getting-active-directory-users-data-via-powershell/
389
+ # @see http://stackoverflow.com/questions/17548523/the-term-get-aduser-is-not-recognized-as-the-name-of-a-cmdlet
390
+ #
391
+ # Just for reference, we could also use ADSI (Active Directory Service Interfaces)
392
+ # @see https://mcpmag.com/articles/2015/04/15/reporting-on-local-accounts.aspx
393
+ class WindowsUser < UserInfo
394
+ # parse windows account name
395
+ def parse_windows_account(username)
396
+ account = username.split('\\')
397
+ name = account.pop
398
+ domain = account.pop if account.size > 0
399
+ [name, domain]
400
+ end
450
401
 
451
- # not implemented yet
452
- def meta_info(_username)
453
- {
454
- home: nil,
455
- shell: nil,
456
- }
402
+ def identity(username)
403
+ # extract domain/user information
404
+ account, domain = parse_windows_account(username)
405
+
406
+ # TODO: escape content
407
+ if !domain.nil?
408
+ filter = "Name = '#{account}' and Domain = '#{domain}'"
409
+ else
410
+ filter = "Name = '#{account}' and LocalAccount = true"
411
+ end
412
+
413
+ script = <<-EOH
414
+ # find user
415
+ $user = Get-WmiObject Win32_UserAccount -filter "#{filter}"
416
+ # get related groups
417
+ $groups = $user.GetRelated('Win32_Group') | Select-Object -Property Caption, Domain, Name, LocalAccount, SID, SIDType, Status
418
+ # filter user information
419
+ $user = $user | Select-Object -Property Caption, Description, Domain, Name, LocalAccount, Lockout, PasswordChangeable, PasswordExpires, PasswordRequired, SID, SIDType, Status
420
+ # build response object
421
+ New-Object -Type PSObject | `
422
+ Add-Member -MemberType NoteProperty -Name User -Value ($user) -PassThru | `
423
+ Add-Member -MemberType NoteProperty -Name Groups -Value ($groups) -PassThru | `
424
+ ConvertTo-Json
425
+ EOH
426
+
427
+ cmd = inspec.script(script)
428
+
429
+ # cannot rely on exit code for now, successful command returns exit code 1
430
+ # return nil if cmd.exit_status != 0, try to parse json
431
+ begin
432
+ params = JSON.parse(cmd.stdout)
433
+ rescue JSON::ParserError => _e
434
+ return nil
435
+ end
436
+
437
+ user = params['User']['Caption'] unless params['User'].nil?
438
+ groups = params['Groups']
439
+ # if groups is no array, generate one
440
+ groups = [groups] if !groups.is_a?(Array)
441
+ groups = groups.map { |grp| grp['Caption'] } unless params['Groups'].nil?
442
+
443
+ {
444
+ uid: nil,
445
+ user: user,
446
+ gid: nil,
447
+ group: nil,
448
+ groups: groups,
449
+ }
450
+ end
451
+
452
+ # not implemented yet
453
+ def meta_info(_username)
454
+ {
455
+ home: nil,
456
+ shell: nil,
457
+ }
458
+ end
457
459
  end
458
460
  end