Skip to content

Commit 8fc33e8

Browse files
Merge pull request #8599 from luchihoratiu/PUP-11026
(PUP-11026) Fix user password management on macOS Big Sur
2 parents 08206ae + de14d58 commit 8fc33e8

File tree

6 files changed

+171
-58
lines changed

6 files changed

+171
-58
lines changed

acceptance/tests/resource/user/osx_10.4_should_fail_when_modify_home.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
test_name "should not modify the home directory of an user on OS X >= 10.14" do
2-
confine :to, :platform => /osx-10.1[4-9]/
2+
confine :to, :platform => /osx/
33

44
tag 'audit:high',
55
'audit:acceptance' # Could be done as integration tests, but would
@@ -23,7 +23,7 @@
2323
end
2424

2525
step "verify the error message is correct" do
26-
expected_error = /OS X version 10\.1[4-9] does not allow changing home using puppet/
26+
expected_error = /OS X version [0-9\.]+ does not allow changing home using puppet/
2727
user_manifest = resource_manifest('user', user, ensure: 'present', home: "/opt/#{user}")
2828

2929
apply_manifest_on(agent, user_manifest) do |result|

acceptance/tests/resource/user/osx_10.4_should_fail_when_modify_uid.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
test_name "should not modify the uid of an user on OS X >= 10.14" do
2-
confine :to, :platform => /osx-10.1[4-9]/
2+
confine :to, :platform => /osx/
33

44
tag 'audit:high',
55
'audit:acceptance' # Could be done as integration tests, but would
@@ -23,7 +23,7 @@
2323
end
2424

2525
step "verify the error message is correct" do
26-
expected_error = /OS X version 10\.1[4-9] does not allow changing uid using puppet/
26+
expected_error = /OS X version [0-9\.]+ does not allow changing uid using puppet/
2727
user_manifest = resource_manifest('user', user, ensure: 'present', uid: rand(999999))
2828

2929
apply_manifest_on(agent, user_manifest) do |result|
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
test_name "should allow managed macOS users to login" do
2+
3+
confine :to, :platform => /osx/
4+
5+
tag 'audit:high',
6+
'audit:acceptance' # Could be done as integration tests, but would
7+
# require changing the system running the test
8+
# in ways that might require special permissions
9+
# or be harmful to the system running the test
10+
11+
12+
# Two different test cases, with additional environment setup inbetween,
13+
# were added in the same file to save on test run time (managing macOS users
14+
# takes around a minute or so because when retrieving plist data from all users with
15+
# `dscl readall` we also receive thousands of bytes of user avatar image)
16+
agents.each do |agent|
17+
teardown do
18+
on(agent, puppet("resource", "user", 'testuser', "ensure=absent"))
19+
end
20+
21+
# Checking if we can create a user with password, salt and iterations
22+
step "create the test user with password and salt" do
23+
# The password is 'helloworld'
24+
apply_manifest_on(agent, <<-MANIFEST, :catch_failures => true)
25+
user { 'testuser':
26+
ensure => present,
27+
password => '6ce97688468f231845d9d982f1f10832ca0c6c728a77bac51c548af99ebd9b9c62bcba15112a0c7a7e34effbb2e92635650c79c51517d72b083a4eb2a513f51ad1f8ea9556cef22456159c341d8bcd382a91708afaf253c2b727d4c6cd3d29cc26011d5d511154037330ecea0263b1be8c1c13086d029c57344291bd37952b56',
28+
salt => '377e8b60e5fdfe509cad188d5b1b9e40e78b418f8c3f0127620ea69d4c32789c',
29+
iterations => 40000,
30+
}
31+
MANIFEST
32+
end
33+
34+
step "verify the password was set correctly and is able to log in" do
35+
on(agent, "dscl /Local/Default -authonly testuser helloworld", :acceptable_exit_codes => 0)
36+
end
37+
38+
unless agent['platform'] =~ /osx-11/
39+
skip_test "AuthenticationAuthority field fix test is not valid for macOS older than Big Sur (11.0)"
40+
end
41+
42+
# Setting up environment to mimic situation on macOS 11 BigSur
43+
# Prior to macOS BigSur, `dscl . -create` was populating more fields with
44+
# default values, including AuthenticationAuthority which contains the
45+
# ShadowHash type
46+
# Withouth this field, login is not allowed
47+
step "remove AuthenticationAuthority field from user" do
48+
on(agent, "dscl /Local/Default -delete Users/testuser AuthenticationAuthority", :acceptable_exit_codes => 0)
49+
end
50+
51+
step "expect user without AuthenticationAuthority to not be able to log in" do
52+
on(agent, "dscl /Local/Default -authonly testuser helloworld", :acceptable_exit_codes => (1..255))
53+
end
54+
55+
# Expecting Puppet to pick up the missing field and add it to
56+
# make the user usable again
57+
step "change password with different salt and expect AuthenticationAuthority field to be readded" do
58+
# The password is still 'helloworld' but with different salt
59+
apply_manifest_on(agent, <<-MANIFEST, { :catch_failures => true, :debug => true }) do |result|
60+
user { 'testuser':
61+
ensure => present,
62+
password => '7e8e82542a0c06595e99bfe10c6bd219d19dede137c5c2f84bf10d98d83b77302d3c9c7f7e3652d420f562613f582ab62b26a52b9b26d0d032efbd486fd865b3ba4fd8a3512137681ce87d190f8fa7848d941c6080c588528dcb682c763c040ff54992dce204c3e5dda973e7b36f7f50a774e55e99fe4c8ed6b6464614838c13',
63+
salt => '8005b8855a187086a3b59eff925a611ec61d2d66d2e786b7598fe0a0b4b8ffba',
64+
iterations => 40000
65+
}
66+
MANIFEST
67+
68+
assert_match(/User 'testuser' is missing the 'SALTED-SHA512-PBKDF2' AuthenticationAuthority key for ShadowHash/, result.stdout)
69+
assert_match(/Adding 'SALTED-SHA512-PBKDF2' AuthenticationAuthority key for ShadowHash to user 'testuser'/, result.stdout)
70+
end
71+
end
72+
73+
step "verify the password was set correctly and is able to log in" do
74+
on(agent, "dscl /Local/Default -authonly testuser helloworld", :acceptable_exit_codes => 0)
75+
end
76+
end
77+
end

acceptance/tests/resource/user/should_allow_password_salt_iterations_osx.rb

Lines changed: 0 additions & 35 deletions
This file was deleted.

lib/puppet/provider/user/directoryservice.rb

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ def salt=(value)
435435
['home', 'uid', 'gid', 'comment', 'shell'].each do |setter_method|
436436
define_method("#{setter_method}=") do |value|
437437
if @property_hash[setter_method.intern]
438-
if self.class.get_os_version.split('.').last.to_i >= 14 && %w(home uid).include?(setter_method)
438+
if %w(home uid).include?(setter_method)
439439
raise Puppet::Error, "OS X version #{self.class.get_os_version} does not allow changing #{setter_method} using puppet"
440440
end
441441
begin
@@ -536,6 +536,14 @@ def write_password_to_users_plist(value)
536536
if (shadow_hash_data.class == Hash) && (shadow_hash_data.has_key?('SALTED-SHA512'))
537537
shadow_hash_data.delete('SALTED-SHA512')
538538
end
539+
540+
# Starting with macOS 11 Big Sur, the AuthenticationAuthority field
541+
# could be missing entirely and without it the managed user cannot log in
542+
if needs_sha512_pbkdf2_authentication_authority_to_be_added?(users_plist)
543+
Puppet.debug("Adding 'SALTED-SHA512-PBKDF2' AuthenticationAuthority key for ShadowHash to user '#{@resource.name}'")
544+
merge_attribute_with_dscl('Users', @resource.name, 'AuthenticationAuthority', ERB::Util.html_escape(SHA512_PBKDF2_AUTHENTICATION_AUTHORITY))
545+
end
546+
539547
set_salted_pbkdf2(users_plist, shadow_hash_data, 'entropy', value)
540548
end
541549
end
@@ -562,6 +570,17 @@ def get_shadow_hash_data(users_plist)
562570
end
563571
end
564572

573+
# This method will check if authentication_authority key of a user's plist
574+
# needs SALTED_SHA512_PBKDF2 to be added. This is a valid case for macOS 11 (Big Sur)
575+
# where users created with `dscl` started to have this field missing
576+
def needs_sha512_pbkdf2_authentication_authority_to_be_added?(users_plist)
577+
authority = users_plist['authentication_authority']
578+
return false if Puppet::Util::Package.versioncmp(self.class.get_os_version, '11.0.0') < 0 && authority && authority.include?(SHA512_PBKDF2_AUTHENTICATION_AUTHORITY)
579+
580+
Puppet.debug("User '#{@resource.name}' is missing the 'SALTED-SHA512-PBKDF2' AuthenticationAuthority key for ShadowHash")
581+
true
582+
end
583+
565584
# This method will embed the binary plist data comprising the user's
566585
# password hash (and Salt/Iterations value if the OS is 10.8 or greater)
567586
# into the ShadowHashData key of the user's plist.
@@ -667,4 +686,8 @@ def write_to_file(filename, value)
667686
raise Puppet::Error, "Could not write to file #{filename}: #{detail}", detail.backtrace
668687
end
669688
end
689+
690+
private
691+
692+
SHA512_PBKDF2_AUTHENTICATION_AUTHORITY = ';ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2,SRP-RFC5054-4096-SHA512-PBKDF2>'
670693
end

spec/unit/provider/user/directoryservice_spec.rb

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -925,28 +925,75 @@ module Puppet::Util::Plist
925925
}
926926
end
927927

928-
it 'should call set_salted_sha512 on 10.7 when given a salted-SHA512 password hash' do
929-
expect(provider).to receive(:get_users_plist).and_return(sample_users_plist)
930-
expect(provider).to receive(:get_shadow_hash_data).with(sample_users_plist).and_return(sha512_shadowhashdata)
931-
expect(provider.class).to receive(:get_os_version).and_return('10.7')
932-
expect(provider).to receive(:set_salted_sha512).with(sample_users_plist, sha512_shadowhashdata, sha512_password_hash)
933-
provider.write_password_to_users_plist(sha512_password_hash)
928+
before do
929+
allow(provider).to receive(:merge_attribute_with_dscl).with('Users', username, 'AuthenticationAuthority', any_args)
934930
end
935931

936-
it 'should call set_salted_pbkdf2 on 10.8 when given a PBKDF2 password hash' do
937-
expect(provider).to receive(:get_users_plist).and_return(sample_users_plist)
938-
expect(provider).to receive(:get_shadow_hash_data).with(sample_users_plist).and_return(pbkdf2_shadowhashdata)
939-
expect(provider.class).to receive(:get_os_version).and_return('10.8')
940-
expect(provider).to receive(:set_salted_pbkdf2).with(sample_users_plist, pbkdf2_shadowhashdata, 'entropy', pbkdf2_password_hash)
941-
provider.write_password_to_users_plist(pbkdf2_password_hash)
932+
describe 'when on macOS 11 (Big Sur) or greater' do
933+
before do
934+
allow(provider.class).to receive(:get_os_version).and_return('11.0.0')
935+
end
936+
937+
it 'should add salted_sha512_pbkdf2 AuthenticationAuthority key if missing' do
938+
expect(provider).to receive(:get_users_plist).and_return(sample_users_plist)
939+
expect(provider).to receive(:get_shadow_hash_data).with(sample_users_plist).and_return(pbkdf2_shadowhashdata)
940+
expect(provider).to receive(:set_salted_pbkdf2).with(sample_users_plist, pbkdf2_shadowhashdata, 'entropy', pbkdf2_password_hash)
941+
expect(provider).to receive(:needs_sha512_pbkdf2_authentication_authority_to_be_added?).and_return(true)
942+
943+
expect(Puppet).to receive(:debug).with("Adding 'SALTED-SHA512-PBKDF2' AuthenticationAuthority key for ShadowHash to user 'nonexistent_user'")
944+
provider.write_password_to_users_plist(pbkdf2_password_hash)
945+
end
946+
947+
it 'should not add salted_sha512_pbkdf2 AuthenticationAuthority key if not missing' do
948+
expect(provider).to receive(:get_users_plist).and_return(sample_users_plist)
949+
expect(provider).to receive(:get_shadow_hash_data).with(sample_users_plist).and_return(pbkdf2_shadowhashdata)
950+
expect(provider).to receive(:set_salted_pbkdf2).with(sample_users_plist, pbkdf2_shadowhashdata, 'entropy', pbkdf2_password_hash)
951+
expect(provider).to receive(:needs_sha512_pbkdf2_authentication_authority_to_be_added?).and_return(false)
952+
953+
expect(Puppet).not_to receive(:debug).with("Adding 'SALTED-SHA512-PBKDF2' AuthenticationAuthority key for ShadowHash to user 'nonexistent_user'")
954+
provider.write_password_to_users_plist(pbkdf2_password_hash)
955+
end
942956
end
943957

944-
it "should delete the SALTED-SHA512 key in the shadow_hash_data hash if it exists on a 10.8 system and write_password_to_users_plist has been called to set the user's password" do
945-
expect(provider).to receive(:get_users_plist).and_return('users_plist')
946-
expect(provider).to receive(:get_shadow_hash_data).with('users_plist').and_return(sha512_shadowhashdata)
947-
expect(provider.class).to receive(:get_os_version).and_return('10.8')
948-
expect(provider).to receive(:set_salted_pbkdf2).with('users_plist', {}, 'entropy', pbkdf2_password_hash)
949-
provider.write_password_to_users_plist(pbkdf2_password_hash)
958+
describe 'when on macOS version lower than 11' do
959+
before do
960+
allow(provider.class).to receive(:get_os_version)
961+
allow(provider).to receive(:needs_sha512_pbkdf2_authentication_authority_to_be_added?).and_return(false)
962+
end
963+
964+
it 'should not add salted_sha512_pbkdf2 AuthenticationAuthority' do
965+
expect(provider).to receive(:get_users_plist).and_return(sample_users_plist)
966+
expect(provider).to receive(:get_shadow_hash_data).with(sample_users_plist).and_return(pbkdf2_shadowhashdata)
967+
expect(provider).to receive(:set_salted_pbkdf2).with(sample_users_plist, pbkdf2_shadowhashdata, 'entropy', pbkdf2_password_hash)
968+
expect(provider).to receive(:needs_sha512_pbkdf2_authentication_authority_to_be_added?).and_return(false)
969+
970+
expect(Puppet).not_to receive(:debug).with("Adding 'SALTED-SHA512-PBKDF2' AuthenticationAuthority key for ShadowHash to user 'nonexistent_user'")
971+
provider.write_password_to_users_plist(pbkdf2_password_hash)
972+
end
973+
974+
it 'should call set_salted_sha512 on 10.7 when given a salted-SHA512 password hash' do
975+
expect(provider).to receive(:get_users_plist).and_return(sample_users_plist)
976+
expect(provider).to receive(:get_shadow_hash_data).with(sample_users_plist).and_return(sha512_shadowhashdata)
977+
expect(provider.class).to receive(:get_os_version).and_return('10.7')
978+
expect(provider).to receive(:set_salted_sha512).with(sample_users_plist, sha512_shadowhashdata, sha512_password_hash)
979+
provider.write_password_to_users_plist(sha512_password_hash)
980+
end
981+
982+
it 'should call set_salted_pbkdf2 on 10.8 when given a PBKDF2 password hash' do
983+
expect(provider).to receive(:get_users_plist).and_return(sample_users_plist)
984+
expect(provider).to receive(:get_shadow_hash_data).with(sample_users_plist).and_return(pbkdf2_shadowhashdata)
985+
expect(provider.class).to receive(:get_os_version).and_return('10.8')
986+
expect(provider).to receive(:set_salted_pbkdf2).with(sample_users_plist, pbkdf2_shadowhashdata, 'entropy', pbkdf2_password_hash)
987+
provider.write_password_to_users_plist(pbkdf2_password_hash)
988+
end
989+
990+
it "should delete the SALTED-SHA512 key in the shadow_hash_data hash if it exists on a 10.8 system and write_password_to_users_plist has been called to set the user's password" do
991+
expect(provider).to receive(:get_users_plist).and_return('users_plist')
992+
expect(provider).to receive(:get_shadow_hash_data).with('users_plist').and_return(sha512_shadowhashdata)
993+
expect(provider.class).to receive(:get_os_version).and_return('10.8')
994+
expect(provider).to receive(:set_salted_pbkdf2).with('users_plist', {}, 'entropy', pbkdf2_password_hash)
995+
provider.write_password_to_users_plist(pbkdf2_password_hash)
996+
end
950997
end
951998
end
952999

@@ -1203,6 +1250,7 @@ module Puppet::Util::Plist
12031250
before :each do
12041251
allow(provider.class).to receive(:get_all_users).and_return(all_users_hash)
12051252
allow(provider.class).to receive(:get_list_of_groups).and_return(group_plist_hash_guid)
1253+
allow(provider).to receive(:merge_attribute_with_dscl).with('Users', username, 'AuthenticationAuthority', any_args)
12061254
provider.class.prefetch({})
12071255
end
12081256

0 commit comments

Comments
 (0)