Last active
July 14, 2020 13:33
-
-
Save pudquick/6c38ed97a8178ec91c4049b0e20dd69c to your computer and use it in GitHub Desktop.
Revisions
-
pudquick revised this gist
Dec 12, 2018 . 1 changed file with 32 additions and 54 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,19 +1,15 @@ require 'base64' require 'plist' module Chef::Provider::User::DsclMojaveUserExtensions # new for 10.14+ def mac_osx_version_greater_than_10_13? Gem::Version.new(node['platform_version']) > Gem::Version.new('10.13.99') end # updated for 10.14+ def load_current_resource super # fixes bug where chef compared hash to plaintext password # only applies to salted_sha512_pbkdf2, which is in 10.8+ if mac_osx_version_greater_than_10_7? @@ -57,7 +53,7 @@ def load_current_resource # new for 10.14+ # runner for dsimport now that we can't write to user plists directly def run_dsimport(*args) result = shell_out('/usr/bin/dsimport', *(args.compact)) raise(Chef::Exceptions::DsimportCommandFailed, "dsimport error: #{result.inspect}") unless result.exitstatus == 0 result.stdout @@ -66,25 +62,13 @@ def run_dsimport(*args) # new for 10.14+ # runner for dscl in plist mode def run_dscl_plist(*args) result = shell_out('/usr/bin/dscl', '-plist', '.', "-#{args[0]}", *((args[1..-1]).compact)) return '' if ( result.exitstatus != 0 ) # Unlike run_dscl, we don't want to raise an error here result.stdout end # new for 10.14+ # the output of dscl -plist isn't identical to reading the user # .plist XML file directly, this repairs the portions we care about @@ -108,30 +92,31 @@ def reformat_user_info(user_hash) end # patched for 10.14+ def create_user # if we're not on 10.14+, return prior behavior unless mac_osx_version_greater_than_10_13? return super end dscl_create_user # set_password modifies the plist file of the user directly. So update # the password first before making any modifications to the user. set_password dscl_create_comment # dscl_set_uid - it is illegal to change the uid after the user is created dscl_set_gid dscl_set_home dscl_set_shell end # patched for 10.14+ def dscl_create_user # if we're not on 10.14+, return prior behavior unless mac_osx_version_greater_than_10_13? return super end # We now need to figure out and specify the uid at creation time new_resource.uid(get_free_uid) if new_resource.uid.nil? || new_resource.uid == "" run_dscl("create", "/Users/#{new_resource.username}", "UniqueID", new_resource.uid) end # patched for 10.14+ @@ -143,7 +128,7 @@ def read_user_info # We flush the cache here in order to make sure that we read # fresh information for the user. shell_out('/usr/bin/dscacheutil', '-flushcache') user_info = nil begin @@ -169,7 +154,7 @@ def set_password # Shadow is saved as binary plist. Convert the info to binary plist. shadow_info_binary = StringIO.new shell_out('/usr/bin/plutil', '-convert', 'binary1', '-o', '-', '-', input: shadow_info.to_plist, live_stream: shadow_info_binary) @@ -235,21 +220,19 @@ def set_password end end module Chef::Provider::Group::DsclMojaveGroupExtensions # new for 10.14+ def mac_osx_version_greater_than_10_13? Gem::Version.new(node['platform_version']) > Gem::Version.new('10.13.99') end # new for 10.14+ # runner for dseditgroup manipulations def run_dseditgroup(*args) # Ensure that our information is accurate shell_out('/usr/bin/dscacheutil', '-flushcache') result = shell_out('/usr/sbin/dseditgroup', '-o', 'edit', '-n', '/Local/Default', '-t', 'user', *(args.compact)) raise(Chef::Exceptions::DseditgroupCommandFailed, "dseditgroup error: #{result.inspect}") unless result.exitstatus == 0 result.stdout @@ -298,20 +281,15 @@ def set_members end end end end class Chef class Provider class User class Dscl prepend Chef::Provider::User::DsclMojaveUserExtensions prepend Chef::Provider::Group::DsclMojaveGroupExtensions end end end end -
pudquick created this gist
Oct 19, 2018 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,317 @@ require 'base64' require 'plist' module Chef::Provider::User::DsclUserMojaveExtensions # new for 10.14+ def mac_osx_version_greater_than_10_13? # eventually we'll use this for all versions, so unlock for CPE & trusted return true if node.gk?(['cpe_trusted_testers', 'cpe']) Gem::Version.new(node['platform_version']) > Gem::Version.new('10.13.99') end # updated for 10.14+ # this fixes this bug: https://github.com/chef/chef/issues/5777 def load_current_resource super current_resource.home('') if current_resource.home.nil? # fixes bug where chef compared hash to plaintext password # only applies to salted_sha512_pbkdf2, which is in 10.8+ if mac_osx_version_greater_than_10_7? if !new_resource.password.nil? && !current_resource.password.nil? # only run if we have passwords to compare if !salted_sha512_pbkdf2?(new_resource.password) # if we're not using a hex hash but instead a real password if salted_sha512_pbkdf2_password_match? # if the hash matches the password, make the resource.password match current_resource.password(new_resource.password) end end end end current_resource end # Brought into the extension namespace DSCL_PROPERTY_MAP = Chef::Provider::User::Dscl::DSCL_PROPERTY_MAP # new for 10.14+ # mapping for raw DS attribute names, which dscl outputs DSCL_RAW_PROPERTY_MAP = { uid: 'dsAttrTypeStandard:UniqueID', gid: 'dsAttrTypeStandard:PrimaryGroupID', home: 'dsAttrTypeStandard:NFSHomeDirectory', shell: 'dsAttrTypeStandard:UserShell', comment: 'dsAttrTypeStandard:RealName', password: 'dsAttrTypeStandard:Password', auth_authority: 'dsAttrTypeStandard:AuthenticationAuthority', shadow_hash: 'dsAttrTypeNative:ShadowHashData', }.freeze # new for 10.14+ # array of data type attributes that dscl improperly outputs as strings # that we need to repair DSCL_DATA_KEYS = [ 'dsAttrTypeNative:ShadowHashData', ].freeze # new for 10.14+ # runner for dsimport now that we can't write to user plists directly def run_dsimport(*args) result = shell_out_compact('/usr/bin/dsimport', args) raise(Chef::Exceptions::DsimportCommandFailed, "dsimport error: #{result.inspect}") unless result.exitstatus == 0 result.stdout end # new for 10.14+ # runner for dscl in plist mode def run_dscl_plist(*args) result = shell_out_compact('/usr/bin/dscl', '-plist', '.', "-#{args[0]}", args[1..-1]) return '' if ( result.exitstatus != 0 ) # Unlike run_dscl, we don't want to raise an error here result.stdout end # new for 10.14+ # runner for dseditgroup manipulations def run_dseditgroup(*args) # Ensure that our information is accurate shell_out_compact('/usr/bin/dscacheutil', '-flushcache') result = shell_out_compact('/usr/sbin/dseditgroup', '-o', 'edit', '-n', '/Local/Default', '-t', 'user', args) raise(Chef::Exceptions::DseditgroupCommandFailed, "dseditgroup error: #{result.inspect}") unless result.exitstatus == 0 result.stdout end # new for 10.14+ # the output of dscl -plist isn't identical to reading the user # .plist XML file directly, this repairs the portions we care about def reformat_user_info(user_hash) return if user_hash.nil? user_info = {} user_hash.each do |k,v| if DSCL_DATA_KEYS.include?(k) # this key is usually a data key, fix the value if we detect it to be if v.first.match('^(\h+ ?)+$') v = [StringIO.new([v.first.delete(' ')].pack('H*'))] end end if DSCL_RAW_PROPERTY_MAP.has_value?(k) # remap keys to match what they were in the XML .plist k = DSCL_PROPERTY_MAP[DSCL_RAW_PROPERTY_MAP.key(k)] end user_info[k] = v end user_info end # patched for 10.14+ def remove_user # if we're not on 10.14+, return prior behavior unless mac_osx_version_greater_than_10_13? return super end # We can't safely do this in 10.14, due to TCC # if new_resource.manage_home # # Remove home directory # FileUtils.rm_rf(current_resource.home) # end # Remove the user from its groups run_dscl('list', '/Groups').each_line do |group| if member_of_group?(group.chomp) # This ensures removal from both GroupMembership and # GroupMembers without needing to know the GeneratedUID run_dseditgroup('-d', new_resource.username, group.chomp) end end # Remove user account run_dscl('delete', "/Users/#{new_resource.username}") end # patched for 10.14+ def read_user_info # if we're not on 10.14+, return prior behavior unless mac_osx_version_greater_than_10_13? return super end # We flush the cache here in order to make sure that we read # fresh information for the user. shell_out_compact('/usr/bin/dscacheutil', '-flushcache') user_info = nil begin user_plist = run_dscl_plist('read', "/Users/#{new_resource.username}") user_record = Plist.parse_xml(user_plist) user_info = reformat_user_info(user_record) rescue Chef::Exceptions::PlistUtilCommandFailed end user_info end # patched for 10.14+ def set_password # if we're not on 10.14+, return prior behavior unless mac_osx_version_greater_than_10_13? return super end # Return if there is no password to set return if new_resource.password.nil? shadow_info = prepare_password_shadow_info # Shadow is saved as binary plist. Convert the info to binary plist. shadow_info_binary = StringIO.new shell_out_compact('/usr/bin/plutil', '-convert', 'binary1', '-o', '-', '-', input: shadow_info.to_plist, live_stream: shadow_info_binary) if user_info.nil? # User is just created. read_user_info() will read the fresh # information for the user with a cache flush. However with # experimentation we've seen that dscl cache is not immediately # updated after the creation of the user. # This is odd and needs to be investigated further. sleep 3 @user_info = read_user_info end # Replace the shadow info in user's plist dscl_set(user_info, :shadow_hash, shadow_info_binary) # 10.14 removed the ability to write to user plists directly # instead, we need to use dsimport to merge the value into the record begin t_name = "#{Chef::Config['file_cache_path']}/shash.tmp" b64_shadow = ::Base64.strict_encode64(shadow_info_binary.string) # the dsimport record format is: # record definition delimiter (space in hex) # escape delimiter (backslash in hex) # record value delimiter (colon in hex) # record array value delimimter (comma in hex) # OpenDirectory record type # number of attributes per record # [delimited list of record attribute names] # we are defining a minimal record: record name + shadowhashdata t_user = 'dsRecTypeStandard:Users' r_name = 'dsAttrTypeStandard:RecordName' r_shad = 'base64:dsAttrTypeNative:ShadowHashData' t_dsimport = <<~HEREDOC 0x0A 0x5C 0x3A 0x2C #{t_user} 2 #{r_name} #{r_shad} #{new_resource.username}:#{b64_shadow} HEREDOC # unfortunately dsimport only works with real files using mmap # so we ensure that the file does not exist already by using EXCL # to fail on open (like a lock file) to make sure we have full # control and ensure 0600 permissions during its usage exclusive_mode = ::File::WRONLY|::File::CREAT|::File::EXCL ::File.delete(t_name) if ::File.exist?(t_name) ::File.open(t_name, exclusive_mode, 0600) do |f| f.write t_dsimport end result = run_dscl('delete', "/Users/#{new_resource.username}", 'ShadowHashData') result = run_dsimport(t_name, '/Local/Default', 'M') ::File.delete(t_name) if ::File.exist?(t_name) result = run_dscl('create', "/Users/#{new_resource.username}", 'Password', '********') rescue => e # if there's an error, delete the temp file ::File.delete(t_name) if ::File.exist?(t_name) log_fatal( :exception => e, :message => '[User::Dscl::set_password] Exception with hash: ' + new_resource.username, ) end end end module Chef::Provider::Group::DsclGroupMojaveExtensions # new for 10.14+ def mac_osx_version_greater_than_10_13? # eventually we'll use this for all versions, so unlock for CPE & trusted return true if node.gk?(['cpe_trusted_testers', 'cpe']) Gem::Version.new(node['platform_version']) > Gem::Version.new('10.13.99') end # new for 10.14+ # runner for dseditgroup manipulations def run_dseditgroup(*args) # Ensure that our information is accurate shell_out_compact('/usr/bin/dscacheutil', '-flushcache') result = shell_out_compact('/usr/sbin/dseditgroup', '-o', 'edit', '-n', '/Local/Default', '-t', 'user', args) raise(Chef::Exceptions::DseditgroupCommandFailed, "dseditgroup error: #{result.inspect}") unless result.exitstatus == 0 result.stdout end # patched for 10.14+ def set_members unless mac_osx_version_greater_than_10_13? return super end # First reset the memberships if the append is not set unless new_resource.append logger.trace("#{new_resource} removing group members #{current_resource.members.join(' ')}") unless current_resource.members.empty? safe_dscl("create", "/Groups/#{new_resource.group_name}", "GroupMembers", "") # clear guid list safe_dscl("create", "/Groups/#{new_resource.group_name}", "GroupMembership", "") # clear user list current_resource.members([ ]) end # Add any members that need to be added if new_resource.members && !new_resource.members.empty? members_to_be_added = [ ] new_resource.members.each do |member| members_to_be_added << member unless current_resource.members.include?(member) end unless members_to_be_added.empty? logger.trace("#{new_resource} setting group members #{members_to_be_added.join(', ')}") # safe_dscl("append", "/Groups/#{new_resource.group_name}", "GroupMembership", *members_to_be_added) members_to_be_added.each do |username| run_dseditgroup('-a', username, new_resource.group_name) end end end # Remove any members that need to be removed if new_resource.excluded_members && !new_resource.excluded_members.empty? members_to_be_removed = [ ] new_resource.excluded_members.each do |member| members_to_be_removed << member if current_resource.members.include?(member) end unless members_to_be_removed.empty? logger.trace("#{new_resource} removing group members #{members_to_be_removed.join(', ')}") # safe_dscl("delete", "/Groups/#{new_resource.group_name}", "GroupMembership", *members_to_be_removed) members_to_be_removed.each do |username| run_dseditgroup('-d', username, new_resource.group_name) end end end end end class Chef class Provider class User class Dscl prepend Chef::Provider::User::DsclUserMojaveExtensions end end class Group class Dscl prepend Chef::Provider::Group::DsclGroupMojaveExtensions end end end end