Skip to content

Commit 5c8472c

Browse files
committed
(PUP-11067) Prioritize user type ensure before purge_ssh_keys
This commit better guards against missing or invalid homedir of a user when the `purge_ssh_keys` parameter is set in a manifest. It also prioritizes the `ensure` field (when it is set to `present`) instead of `purge_ssh_keys`.
1 parent 8d21da1 commit 5c8472c

File tree

3 files changed

+169
-20
lines changed

3 files changed

+169
-20
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
test_name 'should manage purge_ssh_keys for new user' do
2+
3+
tag 'audit:high',
4+
'audit:acceptance'
5+
6+
name = "usr#{rand(9999).to_i}"
7+
8+
agents.each do |agent|
9+
teardown do
10+
on(agent, puppet_resource('user', "#{name}", 'ensure=absent'))
11+
end
12+
13+
home = agent.tmpdir(name)
14+
authorized_keys_file = agent.tmpfile("authorized_keys")
15+
16+
step "create user #{name} with ssh keys purged and expect no failure" do
17+
apply_manifest_on(agent, <<-MANIFEST, { :catch_failures => true, :debug => true }) do |result|
18+
user {'#{name}':
19+
ensure => present,
20+
home => '#{home}',
21+
purge_ssh_keys => true
22+
}
23+
MANIFEST
24+
25+
assert_no_match(/User '#{name}' has no home directory set to purge ssh keys from./, result.stdout)
26+
end
27+
end
28+
29+
step "remove user #{name} and purge ssh keys purged and expect no failure" do
30+
apply_manifest_on(agent, <<-MANIFEST, { :catch_failures => true, :debug => true }) do |result|
31+
user {'#{name}':
32+
ensure => absent,
33+
home => '#{home}',
34+
purge_ssh_keys => true
35+
}
36+
MANIFEST
37+
38+
assert_no_match(/User '#{name}' has no home directory set to purge ssh keys from./, result.stdout)
39+
end
40+
end
41+
42+
# Platforms such as macOS does not support the `managehome` parameter
43+
# which we're expecting to remove homedir when ensure of user is set
44+
# to absent
45+
step "remove homedir" do
46+
agent.rm_rf(home)
47+
end
48+
49+
step "expect debug log with home directory missing on second run of same manifest" do
50+
apply_manifest_on(agent, <<-MANIFEST, { :catch_failures => true, :debug => true }) do |result|
51+
user {'#{name}':
52+
ensure => absent,
53+
home => '#{home}',
54+
purge_ssh_keys => true
55+
}
56+
MANIFEST
57+
58+
assert_match(/User '#{name}' has no home directory set to purge ssh keys from./, result.stdout)
59+
end
60+
end
61+
62+
step "expect debug log with home directory missing when purge_ssh_keys has relative path to home" do
63+
apply_manifest_on(agent, <<-MANIFEST, { :catch_failures => true, :debug => true }) do |result|
64+
user {'#{name}':
65+
ensure => absent,
66+
purge_ssh_keys => '~/authorized_keys'
67+
}
68+
MANIFEST
69+
70+
assert_match(/User '#{name}' has no home directory set to purge ssh keys from./, result.stdout)
71+
end
72+
end
73+
74+
step "expect no debug log with home directory missing when purge_ssh_keys has absolute path" do
75+
apply_manifest_on(agent, <<-MANIFEST, { :catch_failures => true, :debug => true }) do |result|
76+
user {'#{name}':
77+
ensure => absent,
78+
purge_ssh_keys => '#{authorized_keys_file}'
79+
}
80+
MANIFEST
81+
82+
assert_no_match(/User '#{name}' has no home directory set to purge ssh keys from./, result.stdout)
83+
end
84+
end
85+
end
86+
end

lib/puppet/type/user.rb

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ module Puppet
6767
newproperty(:ensure, :parent => Puppet::Property::Ensure) do
6868
newvalue(:present, :event => :user_created) do
6969
provider.create
70+
@resource.generate
7071
end
7172

7273
newvalue(:absent, :event => :user_removed) do
@@ -695,6 +696,7 @@ def delimiter
695696

696697
def generate
697698
if !self[:purge_ssh_keys].empty?
699+
return [] if self[:ensure] == :present && !provider.exists?
698700
if Puppet::Type.type(:ssh_authorized_key).nil?
699701
warning _("Ssh_authorized_key type is not available. Cannot purge SSH keys.")
700702
else
@@ -743,25 +745,6 @@ def generate
743745
end
744746
raise ArgumentError, _("purge_ssh_keys must be true, false, or an array of file names, not %{value}") % { value: value.inspect }
745747
end
746-
747-
munge do |value|
748-
# Resolve string, boolean and symbol forms of true and false to a
749-
# single representation.
750-
test_sym = value.to_s.intern
751-
value = test_sym if [:true, :false].include? test_sym
752-
753-
return [] if value == :false
754-
home = resource[:home] || Dir.home(resource[:name])
755-
756-
return [ "#{home}/.ssh/authorized_keys" ] if value == :true
757-
# value is an array - munge each value
758-
[ value ].flatten.map do |entry|
759-
# make sure frozen value is duplicated by using a gsub, second mutating gsub! is then ok
760-
entry = entry.gsub(/^~\//, "#{home}/")
761-
entry.gsub!(/^%h\//, "#{home}/")
762-
entry
763-
end
764-
end
765748
end
766749

767750
newproperty(:loginclass, :required_features => :manages_loginclass) do
@@ -783,7 +766,7 @@ def generate
783766
# @see generate
784767
# @api private
785768
def find_unmanaged_keys
786-
self[:purge_ssh_keys].
769+
munged_unmanaged_keys.
787770
select { |f| File.readable?(f) }.
788771
map { |f| unknown_keys_in_file(f) }.
789772
flatten.each do |res|
@@ -795,6 +778,41 @@ def find_unmanaged_keys
795778
end
796779
end
797780

781+
def munged_unmanaged_keys
782+
value = self[:purge_ssh_keys]
783+
784+
# Resolve string, boolean and symbol forms of true and false to a
785+
# single representation.
786+
test_sym = value.to_s.intern
787+
value = test_sym if [:true, :false].include? test_sym
788+
789+
return [] if value == :false
790+
791+
home = self[:home]
792+
begin
793+
home ||= provider.home
794+
rescue
795+
Puppet.debug("User '#{self[:name]}' does not exist")
796+
end
797+
798+
if home.to_s.empty? || !Dir.exist?(home.to_s)
799+
if value == :true || [ value ].flatten.any? { |v| v.start_with?('~/', '%h/') }
800+
Puppet.debug("User '#{self[:name]}' has no home directory set to purge ssh keys from.")
801+
return []
802+
end
803+
end
804+
805+
return [ "#{home}/.ssh/authorized_keys" ] if value == :true
806+
807+
# value is an array - munge each value
808+
[ value ].flatten.map do |entry|
809+
# make sure frozen value is duplicated by using a gsub, second mutating gsub! is then ok
810+
entry = entry.gsub(/^~\//, "#{home}/")
811+
entry.gsub!(/^%h\//, "#{home}/")
812+
entry
813+
end
814+
end
815+
798816
# Parse an ssh authorized keys file superficially, extract the comments
799817
# on the keys. These are considered names of possible ssh_authorized_keys
800818
# resources. Keys that are managed by the present catalog are ignored.

spec/unit/type/user_spec.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,51 @@ def self.instances; []; end
174174
end
175175
end
176176

177+
describe "when managing the purge_ssh_keys property" do
178+
context "with valid input" do
179+
it "should support a :true value" do
180+
expect { described_class.new(:name => 'foo', :purge_ssh_keys => :true) }.to_not raise_error
181+
end
182+
183+
it "should support a :false value" do
184+
expect { described_class.new(:name => 'foo', :purge_ssh_keys => :false) }.to_not raise_error
185+
end
186+
187+
it "should support a String value" do
188+
expect { described_class.new(:name => 'foo', :purge_ssh_keys => File.expand_path('home/foo/.ssh/authorized_keys')) }.to_not raise_error
189+
end
190+
191+
it "should support an Array value" do
192+
expect { described_class.new(:name => 'foo', :purge_ssh_keys => [File.expand_path('home/foo/.ssh/authorized_keys'),
193+
File.expand_path('custom/authorized_keys')]) }.to_not raise_error
194+
end
195+
end
196+
197+
context "with faulty input" do
198+
it "should raise error for relative path" do
199+
expect { described_class.new(:name => 'foo', :purge_ssh_keys => 'home/foo/.ssh/authorized_keys') }.to raise_error(Puppet::ResourceError,
200+
/Paths to keyfiles must be absolute/ )
201+
end
202+
203+
it "should raise error for invalid type" do
204+
expect { described_class.new(:name => 'foo', :purge_ssh_keys => :invalid) }.to raise_error(Puppet::ResourceError,
205+
/purge_ssh_keys must be true, false, or an array of file names/ )
206+
end
207+
208+
it "should raise error for array with relative path" do
209+
expect { described_class.new(:name => 'foo', :purge_ssh_keys => ['home/foo/.ssh/authorized_keys',
210+
File.expand_path('custom/authorized_keys')]) }.to raise_error(Puppet::ResourceError,
211+
/Paths to keyfiles must be absolute/ )
212+
end
213+
214+
it "should raise error for array with invalid type" do
215+
expect { described_class.new(:name => 'foo', :purge_ssh_keys => [:invalid,
216+
File.expand_path('custom/authorized_keys')]) }.to raise_error(Puppet::ResourceError,
217+
/Each entry for purge_ssh_keys must be a string/ )
218+
end
219+
end
220+
end
221+
177222
describe "when managing the uid property" do
178223
it "should convert number-looking strings into actual numbers" do
179224
expect(described_class.new(:name => 'foo', :uid => '50')[:uid]).to eq(50)

0 commit comments

Comments
 (0)