class Inspec::Resources::WindowsUser
This optimization was inspired by @see mcpmag.com/articles/2015/04/15/reporting-on-local-accounts.aspx Alternative solutions are WMI
Win32_UserAccount @see msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx @see msdn.microsoft.com/en-us/library/aa394153(v=vs.85).aspx
Public Instance Methods
collect_user_details()
click to toggle source
msdn.microsoft.com/en-us/library/aa746340(v=vs.85).aspx
# File lib/inspec/resources/users.rb, line 664 def collect_user_details # rubocop:disable Metrics/MethodLength return @users_cache if defined?(@users_cache) script = <<~EOH Function ConvertTo-SID { Param([byte[]]$BinarySID) (New-Object System.Security.Principal.SecurityIdentifier($BinarySID,0)).Value } Function Convert-UserFlag { Param ($UserFlag) $List = @() Switch ($UserFlag) { ($UserFlag -BOR 0x0001) { $List += 'SCRIPT' } ($UserFlag -BOR 0x0002) { $List += 'ACCOUNTDISABLE' } ($UserFlag -BOR 0x0008) { $List += 'HOMEDIR_REQUIRED' } ($UserFlag -BOR 0x0010) { $List += 'LOCKOUT' } ($UserFlag -BOR 0x0020) { $List += 'PASSWD_NOTREQD' } ($UserFlag -BOR 0x0040) { $List += 'PASSWD_CANT_CHANGE' } ($UserFlag -BOR 0x0080) { $List += 'ENCRYPTED_TEXT_PWD_ALLOWED' } ($UserFlag -BOR 0x0100) { $List += 'TEMP_DUPLICATE_ACCOUNT' } ($UserFlag -BOR 0x0200) { $List += 'NORMAL_ACCOUNT' } ($UserFlag -BOR 0x0800) { $List += 'INTERDOMAIN_TRUST_ACCOUNT' } ($UserFlag -BOR 0x1000) { $List += 'WORKSTATION_TRUST_ACCOUNT' } ($UserFlag -BOR 0x2000) { $List += 'SERVER_TRUST_ACCOUNT' } ($UserFlag -BOR 0x10000) { $List += 'DONT_EXPIRE_PASSWORD' } ($UserFlag -BOR 0x20000) { $List += 'MNS_LOGON_ACCOUNT' } ($UserFlag -BOR 0x40000) { $List += 'SMARTCARD_REQUIRED' } ($UserFlag -BOR 0x80000) { $List += 'TRUSTED_FOR_DELEGATION' } ($UserFlag -BOR 0x100000) { $List += 'NOT_DELEGATED' } ($UserFlag -BOR 0x200000) { $List += 'USE_DES_KEY_ONLY' } ($UserFlag -BOR 0x400000) { $List += 'DONT_REQ_PREAUTH' } ($UserFlag -BOR 0x800000) { $List += 'PASSWORD_EXPIRED' } ($UserFlag -BOR 0x1000000) { $List += 'TRUSTED_TO_AUTH_FOR_DELEGATION' } ($UserFlag -BOR 0x04000000) { $List += 'PARTIAL_SECRETS_ACCOUNT' } } $List } $Computername = $Env:Computername $adsi = [ADSI]"WinNT://$Computername" $adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach { New-Object PSObject -property @{ uid = ConvertTo-SID -BinarySID $_.ObjectSID[0] username = $_.Name[0] description = $_.Description[0] disabled = $_.AccountDisabled[0] userflags = Convert-UserFlag -UserFlag $_.UserFlags[0] passwordage = [math]::Round($_.PasswordAge[0]/86400) minpasswordlength = $_.MinPasswordLength[0] mindays = [math]::Round($_.MinPasswordAge[0]/86400) maxdays = [math]::Round($_.MaxPasswordAge[0]/86400) warndays = $null badpasswordattempts = $_.BadPasswordAttempts[0] maxbadpasswords = $_.MaxBadPasswordsAllowed[0] gid = $null group = $null groups = @($_.Groups() | Foreach-Object { $_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null) }) home = $_.HomeDirectory[0] shell = $null domain = $Computername lastlogin = if($_.lastlogin.getType().Tostring() -eq "System.Management.Automation.PSMethod" ){ $null }else{[String]$_.lastlogin} } } | ConvertTo-Json EOH cmd = inspec.powershell(script) # cannot rely on exit code for now, successful command returns exit code 1 # return nil if cmd.exit_status != 0, try to parse json begin users = JSON.parse(cmd.stdout) rescue JSON::ParserError => _e return nil end # ensure we have an array of groups users = [users] unless users.is_a?(Array) # convert keys to symbols @users_cache = users.map { |user| user.each_with_object({}) { |(k, v), h| h[k.to_sym] = v } } end
credentials(username)
click to toggle source
# File lib/inspec/resources/users.rb, line 643 def credentials(username) res = identity(username) return if res.nil? { mindays: res[:mindays], maxdays: res[:maxdays], warndays: res[:warndays], badpasswordattempts: res[:badpasswordattempts], maxbadpasswords: res[:maxbadpasswords], minpasswordlength: res[:minpasswordlength], passwordage: res[:passwordage], } end
identity(username)
click to toggle source
# File lib/inspec/resources/users.rb, line 620 def identity(username) # TODO: we look for local users only at this point name, _domain = parse_windows_account(username) return if collect_user_details.nil? res = collect_user_details.select { |user| user[:username] == name } res[0] unless res.empty? end
list_users()
click to toggle source
# File lib/inspec/resources/users.rb, line 659 def list_users collect_user_details.map { |user| user[:username] } end
meta_info(username)
click to toggle source
# File lib/inspec/resources/users.rb, line 629 def meta_info(username) res = identity(username) return if res.nil? { home: res[:home], shell: res[:shell], domain: res[:domain], userflags: res[:userflags], lastlogin: res[:lastlogin], } end
parse_windows_account(username)
click to toggle source
# File lib/inspec/resources/users.rb, line 613 def parse_windows_account(username) account = username.split("\\") name = account.pop domain = account.pop unless account.empty? [name, domain] end