Module: SystemUsers::Fact

Defined in:
lib/facter/user_audit.rb

Class Method Summary collapse

Class Method Details

.add_factObject



12
13
14
15
16
17
18
19
20
21
22
# File 'lib/facter/user_audit.rb', line 12

def self.add_fact()
  Facter.add(:user_audit) do
    confine :kernel do |value|
      value != "windows"
    end

    setcode do
      SystemUsers::Fact::run_fact()
    end
  end
end

.empty_passwordObject



209
210
211
212
213
214
215
216
217
# File 'lib/facter/user_audit.rb', line 209

def self.empty_password()
  if Facter.value(:os)['family'] == 'AIX'
    bad_users = empty_password_aix
  else
    bad_users = empty_password_regular
  end

  bad_users
end

.empty_password_aixObject

AIX handles passwords differently to regular solaris and linux boxes - it has a custom shadow file at /etc/security/passw in its own format. Recommended method to check for empty passwords was ‘pwdck -n ALL` however this seems to return a list of every userid problem except for empty passwords. The file has the format below. In this case user matt has a locked password whereas user geoff is configured to allow access with no password

matt:
  password = *
  lastupdate = 1478218258

geoff:
   password =

Therefore, to find empty passwords, we just need to look for /password =s+$/ and read the line before it



181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/facter/user_audit.rb', line 181

def self.empty_password_aix()
  bad_users = []
  current_user = false
  File.readlines(SystemUsersConstants::SHADOW_FILE_AIX).each { |line|
    if line =~  /^\w+:\s*$/
      current_user = line.tr(": \n",'')
    elsif line =~ /^\s+password\s*=\s*$/
      bad_users << current_user
    end
  }

  bad_users
end

.empty_password_regularObject



195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/facter/user_audit.rb', line 195

def self.empty_password_regular()
  list = File.readlines(SystemUsersConstants::SHADOW_FILE).reject { |line|
    # skip entirely whitespace or commented out
    reject = !!(line =~ /^\s*$/ || line =~ /^\s*#/)
    reject |= line.split(':')[1] != ''

    reject
  }.map { |line|
    line.split(':')[0]
  }

  list
end

.get_dups(filename, col) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/facter/user_audit.rb', line 67

def self.get_dups(filename, col)
  list = File.readlines(filename).reject { |line|
    # skip entirely whitespace or commented out
    line =~ /^\s+$/ || line =~ /^#/
  }.map { |line|
    line.split(':')[col]
  }

  # http://stackoverflow.com/a/8922049
  dups = list.group_by{ |e| e }.select { |k, v| v.size > 1 }.map(&:first)
  dups.sort()
end

.get_uids(max) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/facter/user_audit.rb', line 24

def self.get_uids(max)
  list = File.readlines(SystemUsersConstants::PASSWD_FILE).reject { |line|
    # skip entirely whitespace or commented out
    reject = !!(line =~ /^\s*$/ || line =~ /^\s*#/)

    # skip IDs >= 500 leaving only the low ones
    reject |= line.split(':')[2].to_i >= max

    # skip root
    reject |= line.split(':')[0] == 'root'

    reject
  }.map { |line|
    line.split(':')[0]
  }.uniq.sort
end

.homedirsObject

Return a hash of usernames and homedirs for use later



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/facter/user_audit.rb', line 81

def self.homedirs()
  data = {}
  File.readlines(SystemUsersConstants::PASSWD_FILE).reject { |line|
    # skip entirely whitespace or commented out
    reject = !!(line =~ /^\s*$/ || line =~ /^\s*#/)
  }.each { |line|
    # Fact to contain structured data representing the homedir
    # "don" => {
    #   path => "/home/don",
    #   ensure  => directory,
    #   owner   => "don",
    #   group   => "don",
    #   mode    => "0700",
    # },
    # "mon" => {
    #   path => /home/mon,
    #   ensure  => absent,
    #   owner   => nil,
    #   group   => nil,
    #   mode    => nil,
    # }
    user = line.split(':')[0]
    path  = line.split(':')[5]
    if File.exists?(path)
      if File.symlink?(path)
        type = "link"
      elsif Dir.exists?(path)
        type = "directory"
      else
        type = "file"
      end

      # we may not be able to resolve the UID to a name if we're inside a
      # testcase or things are really broken os just print the UID/GID
      stat  = File.stat(path)
      mode  = "%04o" % (stat.mode & 0777)

      # UID/user
      begin
        owner = Etc.getpwuid(stat.uid).name
      rescue ArgumentError
        owner = stat.uid
      end

      # GID/group
      begin
        group = Etc.getpwuid(stat.gid).name
      rescue ArgumentError
        owner = stat.gid
      end

      # find any world/group writable files in the top level homedir, do NOT
      # perform a complete find as this will take too much resources
      og_write = Dir.glob(File.join(path, "*"), File::FNM_DOTMATCH).reject { |f|
        rej = File.symlink?(f) || f =~ /^\.{1,2}$/
        if ! rej
          stat = File.stat(f)
          rej = ! ((stat.mode & 00002) == 00002) && ! ((stat.mode & 00020) == 00020)
        end

        rej
      }
    else
      type      = "absent"
      owner     = nil
      group     = nil
      mode      = nil
      og_write  = nil
    end
    data[user] = {
      "path"      => path,
      "ensure"    => type,
      "owner"     => owner,
      "group"     => group,
      "mode"      => mode,
      "og_write"  => og_write,
    }
  }

  data
end

.local_usersObject

representation of all users in /etc/passwd



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/facter/user_audit.rb', line 220

def self.local_users()
  data = {}
  File.readlines(SystemUsersConstants::PASSWD_FILE).reject { |line|
    # skip entirely whitespace or commented out
    reject = !!(line =~ /^\s*$/ || line =~ /^\s*#/)

    reject
  }.each { |line|
    fields = line.strip.split(':')

    data[fields[0]] = {
      "uid"     => fields[2],
      "gid"     => fields[3],
      "comment" => fields[4],
      "home"    => fields[5],
      "shell"   => fields[6],
    }

  }

  # if NOT on AIX, read the shadow file and figure out password validity (
  # AIX has its own crazy format and there's no requirement to deal with it
  # right now)
  if Facter.value(:os)['family'] != 'AIX'
    File.readlines(SystemUsersConstants::SHADOW_FILE).reject { |line|
      # skip entirely whitespace or commented out
      reject = !!(line =~ /^\s*$/ || line =~ /^\s*#/)

      reject
    }.each { |line|
      fields = line.strip.split(':')

      if data.include?(fields[0])
        data[fields[0]]["last_change_days"]     = fields[2]
        data[fields[0]]["change_allowed_days"]  = fields[3]
        data[fields[0]]["must_change_days"]     = fields[4]
        data[fields[0]]["warning_days"]         = fields[5]
        data[fields[0]]["expires_days"]         = fields[6]
        data[fields[0]]["disabled_days"]        = fields[7]
      end
    }
  end

  data
end

.low_uidsObject



41
42
43
# File 'lib/facter/user_audit.rb', line 41

def self.low_uids
  get_uids(500)
end

.root_aliasesObject



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/facter/user_audit.rb', line 50

def self.root_aliases
  list = File.readlines(SystemUsersConstants::PASSWD_FILE).reject { |line|
    # skip entirely whitespace or commented out
    reject = !!(line =~ /^\s*$/ || line =~ /^\s*#/)

    # only for UID == 0 (root power)
    reject |= line.split(':')[2] != '0'

    # skip root
    reject |= line.split(':')[0] == 'root'

    reject
  }.map { |line|
    line.split(':')[0]
  }.uniq.sort
end

.run_factObject



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/facter/user_audit.rb', line 266

def self.run_fact()
  {
    :empty_password => empty_password(),
    :low_uids       => low_uids(),
    :system_uids    => system_uids(),
    :homedirs       => homedirs(),
    :local_users    => local_users(),
    :duplicate      => {
      :uid        => get_dups(SystemUsersConstants::PASSWD_FILE, 2),
      :username   => get_dups(SystemUsersConstants::PASSWD_FILE, 0),
      :gid        => get_dups(SystemUsersConstants::GROUP_FILE, 2),
      :groupname  => get_dups(SystemUsersConstants::GROUP_FILE, 0),
      :root_alias => root_aliases(),
    },
  }
end

.system_uidsObject



45
46
47
# File 'lib/facter/user_audit.rb', line 45

def self.system_uids
  get_uids(1000)
end