puppet 2.6.13 → 2.6.14

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

data/CHANGELOG CHANGED
@@ -1,3 +1,22 @@
1
+ 2.6.14
2
+ ===
3
+ d48ad59 Revert "(#5246) Puppetd does not remove it's pidfile when it exits"
4
+ ade5965 Remove unnecessary fallbacks in change_{user,group}
5
+ 0a09a64 Document uid/gid-related methods in Puppet::Util
6
+ 2599d56 Copy owner/group in replace_file
7
+ ead36ff (#12463) eliminate `secure_open` in favour of `replace_file`
8
+ 1469538 (#12460) use `replace_file` for the .k5login file
9
+ 8461203 (#12462) user_role_add: use `replace_file` for /etc/shadow
10
+ 0ad532a (#12463) add secure `replace_file` to Puppet::Util
11
+ 76d0749 (#12459) drop supplementary groups when permanently dropping UID
12
+ 50909b9 (#12458) default to users primary group, not root, in `asuser`
13
+ d00c5cc (#12457) add users primary group, not Process.gid, in initgroups
14
+ d937ae3 (#6541) Use the same filebucket for backup and restore
15
+ a758066 (#11996) Fix test failures due to hash processing order changes.
16
+ e0e31d5 (#5246) Puppetd does not remove it's pidfile when it exits
17
+ 0ab4597 (#11764) Fix failing cron test
18
+ 073ca03 (#11764) Fix cron jobs for passing block to method
19
+
1
20
  2.6.13
2
21
  ===
3
22
  e4ee794 (#10739) Provide default subjectAltNames while bootstrapping master
data/lib/puppet.rb CHANGED
@@ -24,7 +24,7 @@ require 'puppet/util/run_mode'
24
24
  # it's also a place to find top-level commands like 'debug'
25
25
 
26
26
  module Puppet
27
- PUPPETVERSION = '2.6.13'
27
+ PUPPETVERSION = '2.6.14'
28
28
 
29
29
  def Puppet.version
30
30
  PUPPETVERSION
data/lib/puppet/daemon.rb CHANGED
@@ -33,9 +33,9 @@ class Puppet::Daemon
33
33
  Puppet::Util::Log.reopen
34
34
  rescue => detail
35
35
  Puppet.err "Could not start #{Puppet[:name]}: #{detail}"
36
- Puppet::Util::secure_open("/tmp/daemonout", "w") { |f|
36
+ Puppet::Util::replace_file("/tmp/daemonout", 0644) do |f|
37
37
  f.puts "Could not start #{Puppet[:name]}: #{detail}"
38
- }
38
+ end
39
39
  exit(12)
40
40
  end
41
41
  end
@@ -22,7 +22,7 @@ class Puppet::Network::Server
22
22
  $stderr.reopen $stdout
23
23
  Puppet::Util::Log.reopen
24
24
  rescue => detail
25
- Puppet::Util.secure_open("/tmp/daemonout", "w") { |f|
25
+ Puppet::Util.replace_file("/tmp/daemonout", 0644) { |f|
26
26
  f.puts "Could not start #{Puppet[:name]}: #{detail}"
27
27
  }
28
28
  raise "Could not start #{Puppet[:name]}: #{detail}"
@@ -1,3 +1,4 @@
1
+ require 'puppet/util'
1
2
  require 'puppet/util/user_attr'
2
3
 
3
4
  Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source => :useradd do
@@ -145,11 +146,22 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source =>
145
146
  run([command(:modify)] + build_keys_cmd(keys_hash) << @resource[:name], "modify attribute key pairs")
146
147
  end
147
148
 
149
+
150
+ # This helper makes it possible to test this on stub data without having to
151
+ # do too many crazy things!
152
+ def target_file_path
153
+ "/etc/shadow"
154
+ end
155
+ private :target_file_path
156
+
148
157
  #Read in /etc/shadow, find the line for this user (skipping comments, because who knows) and return it
149
158
  #No abstraction, all esoteric knowledge of file formats, yay
150
159
  def shadow_entry
151
160
  return @shadow_entry if defined? @shadow_entry
152
- @shadow_entry = File.readlines("/etc/shadow").reject { |r| r =~ /^[^\w]/ }.collect { |l| l.chomp.split(':') }.find { |user, _| user == @resource[:name] }
161
+ @shadow_entry = File.readlines(target_file_path).
162
+ reject { |r| r =~ /^[^\w]/ }.
163
+ collect { |l| l.chomp.split(':') }.
164
+ find { |user, _| user == @resource[:name] }
153
165
  end
154
166
 
155
167
  def password
@@ -164,28 +176,31 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source =>
164
176
  shadow_entry ? shadow_entry[4] : :absent
165
177
  end
166
178
 
167
- #Read in /etc/shadow, find the line for our used and rewrite it with the new pw
168
- #Smooth like 80 grit
179
+ # Read in /etc/shadow, find the line for our used and rewrite it with the
180
+ # new pw. Smooth like 80 grit sandpaper.
181
+ #
182
+ # Now uses the `replace_file` mechanism to minimize the chance that we lose
183
+ # data, but it is still terrible. We still skip platform locking, so a
184
+ # concurrent `vipw -s` session will have no idea we risk data loss.
169
185
  def password=(cryptopw)
170
186
  begin
171
- File.open("/etc/shadow", "r") do |shadow|
172
- File.open("/etc/shadow_tmp", "w", 0600) do |shadow_tmp|
173
- while line = shadow.gets
174
- line_arr = line.split(':')
175
- if line_arr[0] == @resource[:name]
176
- line_arr[1] = cryptopw
177
- line = line_arr.join(':')
178
- end
179
- shadow_tmp.print line
187
+ shadow = File.read(target_file_path)
188
+
189
+ # Go Mifune loves the race here where we can lose data because
190
+ # /etc/shadow changed between reading it and writing it.
191
+ # --daniel 2012-02-05
192
+ Puppet::Util.replace_file(target_file_path, 0640) do |fh|
193
+ shadow.each_line do |line|
194
+ line_arr = line.split(':')
195
+ if line_arr[0] == @resource[:name]
196
+ line_arr[1] = cryptopw
197
+ line = line_arr.join(':')
180
198
  end
199
+ fh.print line
181
200
  end
182
201
  end
183
- File.rename("/etc/shadow_tmp", "/etc/shadow")
184
202
  rescue => detail
185
- fail "Could not write temporary shadow file: #{detail}"
186
- ensure
187
- # Make sure this *always* gets deleted
188
- File.unlink("/etc/shadow_tmp") if File.exist?("/etc/shadow_tmp")
203
+ fail "Could not write replace #{target_file_path}: #{detail}"
189
204
  end
190
205
  end
191
206
  end
@@ -58,6 +58,6 @@ module Puppet::Rails::Benchmark
58
58
  data = {}
59
59
  end
60
60
  data[branch] = $benchmarks
61
- Puppet::Util.secure_open(file, "w") { |f| f.print YAML.dump(data) }
61
+ Puppet::Util.replace_file(file, 0644) { |f| f.print YAML.dump(data) }
62
62
  end
63
63
  end
@@ -1,4 +1,5 @@
1
1
  # Plug-in type for handling k5login files
2
+ require 'puppet/util'
2
3
 
3
4
  Puppet::Type.newtype(:k5login) do
4
5
  @doc = "Manage the `.k5login` file for a user. Specify the full path to
@@ -79,8 +80,8 @@ Puppet::Type.newtype(:k5login) do
79
80
 
80
81
  private
81
82
  def write(value)
82
- Puppet::Util.secure_open(@resource[:name], "w") do |f|
83
- f.puts value.join("\n")
83
+ Puppet::Util.replace_file(@resource[:name], 0644) do |f|
84
+ f.puts value
84
85
  end
85
86
  end
86
87
  end
data/lib/puppet/util.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  # A module to collect utility functions.
2
-
3
2
  require 'puppet/util/monkey_patches'
4
- require 'sync'
5
3
  require 'puppet/external/lock'
6
- require 'monitor'
7
4
  require 'puppet/util/execution_stub'
5
+ require 'sync'
6
+ require 'monitor'
7
+ require 'tempfile'
8
+ require 'pathname'
8
9
 
9
10
  module Puppet
10
11
  # A command failed to execute.
@@ -261,7 +262,6 @@ module Util
261
262
  output_file="/dev/null"
262
263
  error_file="/dev/null"
263
264
  if ! arguments[:squelch]
264
- require "tempfile"
265
265
  output_file = Tempfile.new("puppet")
266
266
  error_file=output_file if arguments[:combine]
267
267
  end
@@ -287,8 +287,7 @@ module Util
287
287
  $stderr.reopen(error_file)
288
288
 
289
289
  3.upto(256){|fd| IO::new(fd).close rescue nil}
290
- Puppet::Util::SUIDManager.change_group(arguments[:gid], true) if arguments[:gid]
291
- Puppet::Util::SUIDManager.change_user(arguments[:uid], true) if arguments[:uid]
290
+ Puppet::Util::SUIDManager.change_privileges(arguments[:uid], arguments[:gid], true)
292
291
  ENV['LANG'] = ENV['LC_ALL'] = ENV['LC_MESSAGES'] = ENV['LANGUAGE'] = 'C'
293
292
  if command.is_a?(Array)
294
293
  Kernel.exec(*command)
@@ -409,27 +408,82 @@ module Util
409
408
 
410
409
  module_function :memory, :thinmark
411
410
 
412
- def secure_open(file,must_be_w,&block)
413
- raise Puppet::DevError,"secure_open only works with mode 'w'" unless must_be_w == 'w'
414
- raise Puppet::DevError,"secure_open only requires a block" unless block_given?
415
- Puppet.warning "#{file} was a symlink to #{File.readlink(file)}" if File.symlink?(file)
416
- if File.exists?(file) or File.symlink?(file)
417
- wait = File.symlink?(file) ? 5.0 : 0.1
418
- File.delete(file)
419
- sleep wait # give it a chance to reappear, just in case someone is actively trying something.
411
+ # Replace a file, securely. This takes a block, and passes it the file
412
+ # handle of a file open for writing. Write the replacement content inside
413
+ # the block and it will safely replace the target file.
414
+ #
415
+ # This method will make no changes to the target file until the content is
416
+ # successfully written and the block returns without raising an error.
417
+ #
418
+ # As far as possible the state of the existing file, such as mode, is
419
+ # preserved. This works hard to avoid loss of any metadata, but will result
420
+ # in an inode change for the file.
421
+ #
422
+ # Arguments: `filename`, `default_mode`
423
+ #
424
+ # The filename is the file we are going to replace.
425
+ #
426
+ # The default_mode is the mode to use when the target file doesn't already
427
+ # exist; if the file is present we copy the existing mode/owner/group values
428
+ # across.
429
+ def replace_file(file, default_mode, &block)
430
+ raise Puppet::DevError, "replace_file requires a block" unless block_given?
431
+
432
+ file = Pathname(file)
433
+ tempfile = Tempfile.new(file.basename.to_s, file.dirname.to_s)
434
+
435
+ file_exists = file.exist?
436
+
437
+ # If the file exists, use its current mode/owner/group. If it doesn't, use
438
+ # the supplied mode, and default to current user/group.
439
+ if file_exists
440
+ stat = file.lstat
441
+
442
+ # We only care about the four lowest-order octets. Higher octets are
443
+ # filesystem-specific.
444
+ mode = stat.mode & 07777
445
+ uid = stat.uid
446
+ gid = stat.gid
447
+ else
448
+ mode = default_mode
449
+ uid = Process.euid
450
+ gid = Process.egid
420
451
  end
452
+
453
+ # Set properties of the temporary file before we write the content, because
454
+ # Tempfile doesn't promise to be safe from reading by other people, just
455
+ # that it avoids races around creating the file.
456
+ tempfile.chmod(mode)
457
+ tempfile.chown(uid, gid)
458
+
459
+ # OK, now allow the caller to write the content of the file.
460
+ yield tempfile
461
+
462
+ # Now, make sure the data (which includes the mode) is safe on disk.
463
+ tempfile.flush
421
464
  begin
422
- File.open(file,File::CREAT|File::EXCL|File::TRUNC|File::WRONLY,&block)
423
- rescue Errno::EEXIST
424
- desc = File.symlink?(file) ? "symlink to #{File.readlink(file)}" : File.stat(file).ftype
425
- puts "Warning: #{file} was apparently created by another process (as"
426
- puts "a #{desc}) as soon as it was deleted by this process. Someone may be trying"
427
- puts "to do something objectionable (such as tricking you into overwriting system"
428
- puts "files if you are running as root)."
429
- raise
465
+ tempfile.fsync
466
+ rescue NotImplementedError
467
+ # fsync may not be implemented by Ruby on all platforms, but
468
+ # there is absolutely no recovery path if we detect that. So, we just
469
+ # ignore the return code.
470
+ #
471
+ # However, don't be fooled: that is accepting that we are running in
472
+ # an unsafe fashion. If you are porting to a new platform don't stub
473
+ # that out.
430
474
  end
475
+
476
+ tempfile.close
477
+
478
+ File.rename(tempfile.path, file)
479
+
480
+ # Ideally, we would now fsync the directory as well, but Ruby doesn't
481
+ # have support for that, and it doesn't matter /that/ much...
482
+
483
+ # Return something true, and possibly useful.
484
+ file
431
485
  end
432
- module_function :secure_open
486
+ module_function :replace_file
433
487
  end
434
488
  end
435
489
 
@@ -36,14 +36,15 @@ class Puppet::Util::Reference
36
36
 
37
37
  def self.pdf(text)
38
38
  puts "creating pdf"
39
- Puppet::Util.secure_open("/tmp/puppetdoc.txt", "w") do |f|
40
- f.puts text
41
- end
42
- rst2latex = which('rst2latex') || which('rst2latex.py') || raise("Could not find rst2latex")
39
+ rst2latex = which('rst2latex') || which('rst2latex.py') ||
40
+ raise("Could not find rst2latex")
41
+
43
42
  cmd = %{#{rst2latex} /tmp/puppetdoc.txt > /tmp/puppetdoc.tex}
44
- Puppet::Util.secure_open("/tmp/puppetdoc.tex","w") do |f|
45
- # If we get here without an error, /tmp/puppetdoc.tex isn't a tricky cracker's symlink
46
- end
43
+ Puppet::Util.replace_file("/tmp/puppetdoc.txt") {|f| f.puts text }
44
+ # There used to be an attempt to use secure_open / replace_file to secure
45
+ # the target, too, but that did nothing: the race was still here. We can
46
+ # get exactly the same benefit from running this effort:
47
+ File.unlink('/tmp/puppetdoc.tex') rescue nil
47
48
  output = %x{#{cmd}}
48
49
  unless $CHILD_STATUS == 0
49
50
  $stderr.puts "rst2latex failed"
@@ -1,5 +1,6 @@
1
1
  require 'puppet/util/warnings'
2
2
  require 'forwardable'
3
+ require 'etc'
3
4
 
4
5
  module Puppet::Util::SUIDManager
5
6
  include Puppet::Util::Warnings
@@ -40,55 +41,76 @@ module Puppet::Util::SUIDManager
40
41
  Process.uid == 0
41
42
  end
42
43
 
43
- # Runs block setting uid and gid if provided then restoring original ids
44
+ # Methods to handle changing uid/gid of the running process. In general,
45
+ # these will noop or fail on Windows, and require root to change to anything
46
+ # but the current uid/gid (which is a noop).
47
+
48
+ # Runs block setting euid and egid if provided then restoring original ids.
49
+ # If running on Windows or without root, the block will be run with the
50
+ # current euid/egid.
44
51
  def asuser(new_uid=nil, new_gid=nil)
45
- return yield if Puppet.features.microsoft_windows? or !root?
52
+ return yield if Puppet.features.microsoft_windows?
53
+ return yield unless root?
54
+ return yield unless new_uid or new_gid
46
55
 
47
56
  old_euid, old_egid = self.euid, self.egid
48
57
  begin
49
- change_group(new_gid) if new_gid
50
- change_user(new_uid) if new_uid
58
+ change_privileges(new_uid, new_gid, false)
51
59
 
52
60
  yield
53
61
  ensure
54
- change_group(old_egid)
55
- change_user(old_euid)
62
+ change_privileges(new_uid ? old_euid : nil, old_egid, false)
56
63
  end
57
64
  end
58
65
  module_function :asuser
59
66
 
67
+ # If `permanently` is set, will permanently change the uid/gid of the
68
+ # process. If not, it will only set the euid/egid. If only uid is supplied,
69
+ # the primary group of the supplied gid will be used. If only gid is
70
+ # supplied, only gid will be changed. This method will fail if used on
71
+ # Windows.
72
+ def change_privileges(uid=nil, gid=nil, permanently=false)
73
+ return unless uid or gid
74
+
75
+ unless gid
76
+ uid = convert_xid(:uid, uid)
77
+ gid = Etc.getpwuid(uid).gid
78
+ end
79
+
80
+ change_group(gid, permanently)
81
+ change_user(uid, permanently) if uid
82
+ end
83
+ module_function :change_privileges
84
+
85
+ # Changes the egid of the process if `permanently` is not set, otherwise
86
+ # changes gid. This method will fail if used on Windows, or attempting to
87
+ # change to a different gid without root.
60
88
  def change_group(group, permanently=false)
61
89
  gid = convert_xid(:gid, group)
62
90
  raise Puppet::Error, "No such group #{group}" unless gid
63
91
 
64
92
  if permanently
65
- begin
66
- Process::GID.change_privilege(gid)
67
- rescue NotImplementedError
68
- Process.egid = gid
69
- Process.gid = gid
70
- end
93
+ Process::GID.change_privilege(gid)
71
94
  else
72
95
  Process.egid = gid
73
96
  end
74
97
  end
75
98
  module_function :change_group
76
99
 
100
+ # As change_group, but operates on uids. If changing user permanently,
101
+ # supplementary groups will be set the to default groups for the new uid.
77
102
  def change_user(user, permanently=false)
78
103
  uid = convert_xid(:uid, user)
79
104
  raise Puppet::Error, "No such user #{user}" unless uid
80
105
 
81
106
  if permanently
82
- begin
83
- Process::UID.change_privilege(uid)
84
- rescue NotImplementedError
85
- # If changing uid, we must be root. So initgroups first here.
86
- initgroups(uid)
87
- Process.euid = uid
88
- Process.uid = uid
89
- end
107
+ # If changing uid, we must be root. So initgroups first here.
108
+ initgroups(uid)
109
+
110
+ Process::UID.change_privilege(uid)
90
111
  else
91
- # If we're already root, initgroups before changing euid. If we're not,
112
+ # We must be root to initgroups, so initgroups before dropping euid if
113
+ # we're root, otherwise elevate euid before initgroups.
92
114
  # change euid (to root) first.
93
115
  if Process.euid == 0
94
116
  initgroups(uid)
@@ -113,10 +135,13 @@ module Puppet::Util::SUIDManager
113
135
  end
114
136
  module_function :convert_xid
115
137
 
116
- # Initialize supplementary groups
117
- def initgroups(user)
118
- require 'etc'
119
- Process.initgroups(Etc.getpwuid(user).name, Process.gid)
138
+ # Initialize primary and supplemental groups to those of the target user. We
139
+ # take the UID and manually look up their details in the system database,
140
+ # including username and primary group. This method will fail on Windows, or
141
+ # if used without root to initgroups of another user.
142
+ def initgroups(uid)
143
+ pwent = Etc.getpwuid(uid)
144
+ Process.initgroups(pwent.name, pwent.gid)
120
145
  end
121
146
 
122
147
  module_function :initgroups
@@ -126,15 +151,5 @@ module Puppet::Util::SUIDManager
126
151
  [output, $CHILD_STATUS.dup]
127
152
  end
128
153
  module_function :run_and_capture
129
-
130
- def system(command, new_uid=nil, new_gid=nil)
131
- status = nil
132
- asuser(new_uid, new_gid) do
133
- Kernel.system(command)
134
- status = $CHILD_STATUS.dup
135
- end
136
- status
137
- end
138
- module_function :system
139
154
  end
140
155
 
@@ -92,7 +92,6 @@ describe Puppet::Parser::AST::ASTHash do
92
92
 
93
93
  it "should return a valid string with to_s" do
94
94
  hash = Puppet::Parser::AST::ASTHash.new(:value => { "a" => "b", "c" => "d" })
95
-
96
- hash.to_s.should == '{a => b, c => d}'
95
+ ["{a => b, c => d}", "{c => d, a => b}"].should be_include hash.to_s
97
96
  end
98
97
  end
@@ -36,8 +36,11 @@ describe klass do
36
36
  @property.should_to_s({:foo => "baz", :bar => "boo"}) == "foo=baz;bar=boo"
37
37
  end
38
38
 
39
- it "should return the passed in array values joined with the delimiter from is_to_s" do
40
- @property.is_to_s({"foo" => "baz" , "bar" => "boo"}).should == "foo=baz;bar=boo"
39
+ it "should return the passed in hash values joined with the delimiter from is_to_s" do
40
+ s = @property.is_to_s({"foo" => "baz" , "bar" => "boo"})
41
+
42
+ # We can't predict the order the hash is processed in...
43
+ ["foo=baz;bar=boo", "bar=boo;foo=baz"].should be_include s
41
44
  end
42
45
 
43
46
  describe "when calling inclusive?" do
@@ -1,10 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require File.dirname(__FILE__) + '/../../../spec_helper'
4
+ require 'tempfile'
4
5
 
5
6
  provider_class = Puppet::Type.type(:user).provider(:user_role_add)
6
7
 
7
8
  describe provider_class do
9
+ include PuppetSpec::Files
10
+
8
11
  before do
9
12
  @resource = stub("resource", :name => "myuser", :managehome? => nil)
10
13
  @resource.stubs(:should).returns "fakeval"
@@ -244,17 +247,51 @@ describe provider_class do
244
247
  end
245
248
 
246
249
  describe "when setting the password" do
247
- #how can you mock these blocks up?
248
- it "should open /etc/shadow for reading and /etc/shadow_tmp for writing" do
249
- File.expects(:open).with("/etc/shadow", "r")
250
- File.stubs(:rename)
251
- @provider.password=("hashedpassword")
252
- end
253
-
254
- it "should rename the /etc/shadow_tmp to /etc/shadow" do
255
- File.stubs(:open).with("/etc/shadow", "r")
256
- File.expects(:rename).with("/etc/shadow_tmp", "/etc/shadow")
257
- @provider.password=("hashedpassword")
250
+ let(:path) { tmpfile('etc-shadow') }
251
+
252
+ before :each do
253
+ @provider.stubs(:target_file_path).returns(path)
254
+ end
255
+
256
+ def write_fixture(content)
257
+ File.open(path, 'w') { |f| f.print(content) }
258
+ end
259
+
260
+ it "should update the target user" do
261
+ write_fixture <<FIXTURE
262
+ fakeval:seriously:15315:0:99999:7:::
263
+ FIXTURE
264
+ @provider.password = "totally"
265
+ File.read(path).should =~ /^fakeval:totally:/
266
+ end
267
+
268
+ it "should only update the target user" do
269
+ write_fixture <<FIXTURE
270
+ before:seriously:15315:0:99999:7:::
271
+ fakeval:seriously:15315:0:99999:7:::
272
+ fakevalish:seriously:15315:0:99999:7:::
273
+ after:seriously:15315:0:99999:7:::
274
+ FIXTURE
275
+ @provider.password = "totally"
276
+ File.read(path).should == <<EOT
277
+ before:seriously:15315:0:99999:7:::
278
+ fakeval:totally:15315:0:99999:7:::
279
+ fakevalish:seriously:15315:0:99999:7:::
280
+ after:seriously:15315:0:99999:7:::
281
+ EOT
282
+ end
283
+
284
+ # This preserves the current semantics, but is it right? --daniel 2012-02-05
285
+ it "should do nothing if the target user is missing" do
286
+ fixture = <<FIXTURE
287
+ before:seriously:15315:0:99999:7:::
288
+ fakevalish:seriously:15315:0:99999:7:::
289
+ after:seriously:15315:0:99999:7:::
290
+ FIXTURE
291
+
292
+ write_fixture fixture
293
+ @provider.password = "totally"
294
+ File.read(path).should == fixture
258
295
  end
259
296
  end
260
297
 
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env ruby
2
+ require 'spec_helper'
3
+ require 'fileutils'
4
+ require 'puppet/type'
5
+
6
+ describe Puppet::Type.type(:k5login) do
7
+ include PuppetSpec::Files
8
+
9
+ context "the type class" do
10
+ subject { described_class }
11
+ it { should be_validattr :ensure }
12
+ it { should be_validattr :path }
13
+ it { should be_validattr :principals }
14
+ it { should be_validattr :mode }
15
+ # We have one, inline provider implemented.
16
+ it { should be_validattr :provider }
17
+ end
18
+
19
+ let(:path) { tmpfile('k5login') }
20
+
21
+ def resource(attrs = {})
22
+ attrs = {
23
+ :ensure => 'present',
24
+ :path => path,
25
+ :principals => 'fred@EXAMPLE.COM'
26
+ }.merge(attrs)
27
+
28
+ if content = attrs.delete(:content)
29
+ File.open(path, 'w') { |f| f.print(content) }
30
+ end
31
+
32
+ resource = described_class.new(attrs)
33
+ resource
34
+ end
35
+
36
+ before :each do
37
+ FileUtils.touch(path)
38
+ end
39
+
40
+ context "the provider" do
41
+ context "when the file is missing" do
42
+ it "should initially be absent" do
43
+ File.delete(path)
44
+ resource.retrieve[:ensure].must == :absent
45
+ end
46
+
47
+ it "should create the file when synced" do
48
+ resource(:ensure => 'present').parameter(:ensure).sync
49
+ File.should be_exist path
50
+ end
51
+ end
52
+
53
+ context "when the file is present" do
54
+ context "retrieved initial state" do
55
+ subject { resource.retrieve }
56
+
57
+ it "should retrieve its properties correctly with zero principals" do
58
+ subject[:ensure].should == :present
59
+ subject[:principals].should == []
60
+ # We don't really care what the mode is, just that it got it
61
+ subject[:mode].should_not be_nil
62
+ end
63
+
64
+ context "with one principal" do
65
+ subject { resource(:content => "daniel@EXAMPLE.COM\n").retrieve }
66
+
67
+ it "should retrieve its principals correctly" do
68
+ subject[:principals].should == ["daniel@EXAMPLE.COM"]
69
+ end
70
+ end
71
+
72
+ context "with two principals" do
73
+ subject do
74
+ content = ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"].join("\n")
75
+ resource(:content => content).retrieve
76
+ end
77
+
78
+ it "should retrieve its principals correctly" do
79
+ subject[:principals].should == ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"]
80
+ end
81
+ end
82
+ end
83
+
84
+ it "should remove the file ensure is absent" do
85
+ resource(:ensure => 'absent').property(:ensure).sync
86
+ File.should_not be_exist path
87
+ end
88
+
89
+ it "should write one principal to the file" do
90
+ File.read(path).should == ""
91
+ resource(:principals => ["daniel@EXAMPLE.COM"]).property(:principals).sync
92
+ File.read(path).should == "daniel@EXAMPLE.COM\n"
93
+ end
94
+
95
+ it "should write multiple principals to the file" do
96
+ content = ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"]
97
+
98
+ File.read(path).should == ""
99
+ resource(:principals => content).property(:principals).sync
100
+ File.read(path).should == content.join("\n") + "\n"
101
+ end
102
+
103
+ describe "when setting the mode", :unless => Puppet.features.microsoft_windows? do
104
+ # The defined input type is "mode, as an octal string"
105
+ ["400", "600", "700", "644", "664"].each do |mode|
106
+ it "should update the mode to #{mode}" do
107
+ resource(:mode => mode).property(:mode).sync
108
+
109
+ (File.stat(path).mode & 07777).to_s(8).should == mode
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -13,10 +13,18 @@ describe Puppet::Util::SUIDManager do
13
13
 
14
14
  before :each do
15
15
  Puppet::Util::SUIDManager.stubs(:convert_xid).returns(42)
16
- Puppet::Util::SUIDManager.stubs(:initgroups)
16
+ pwent = stub('pwent', :name => 'fred', :uid => 42, :gid => 42)
17
+ Etc.stubs(:getpwuid).with(42).returns(pwent)
17
18
 
18
19
  [:euid, :egid, :uid, :gid, :groups].each do |id|
19
- Process.stubs("#{id}=").with {|value| xids[id] = value}
20
+ Process.stubs("#{id}=").with {|value| xids[id] = value }
21
+ end
22
+ end
23
+
24
+ describe "#initgroups" do
25
+ it "should use the primary group of the user as the 'basegid'" do
26
+ Process.expects(:initgroups).with('fred', 42)
27
+ described_class.initgroups(42)
20
28
  end
21
29
  end
22
30
 
@@ -31,45 +39,93 @@ describe Puppet::Util::SUIDManager do
31
39
  end
32
40
 
33
41
  describe "#asuser" do
34
- it "should set euid/egid when root" do
35
- Process.stubs(:uid).returns(0)
42
+ it "should not get or set euid/egid when not root" do
43
+ Process.stubs(:uid).returns(1)
36
44
 
37
45
  Process.stubs(:egid).returns(51)
38
46
  Process.stubs(:euid).returns(50)
39
47
 
40
- Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51)
41
- Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50)
48
+ Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) {}
49
+
50
+ xids.should be_empty
51
+ end
42
52
 
43
- yielded = false
44
- Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) do
45
- xids[:egid].should == user[:gid]
46
- xids[:euid].should == user[:uid]
47
- yielded = true
53
+ context "when root and not windows" do
54
+ before :each do
55
+ Process.stubs(:uid).returns(0)
56
+ Puppet.features.stubs(:microsoft_windows?).returns(false)
48
57
  end
49
58
 
50
- xids[:egid].should == 51
51
- xids[:euid].should == 50
59
+ it "should set euid/egid when root" do
60
+ Process.stubs(:uid).returns(0)
52
61
 
53
- # It's possible asuser could simply not yield, so the assertions in the
54
- # block wouldn't fail. So verify those actually got checked.
55
- yielded.should be_true
56
- end
62
+ Process.stubs(:egid).returns(51)
63
+ Process.stubs(:euid).returns(50)
57
64
 
58
- it "should not get or set euid/egid when not root" do
59
- Process.stubs(:uid).returns(1)
65
+ Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51)
66
+ Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50)
67
+ Puppet::Util::SUIDManager.stubs(:initgroups).returns([])
60
68
 
61
- Process.stubs(:egid).returns(51)
62
- Process.stubs(:euid).returns(50)
69
+ yielded = false
70
+ Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) do
71
+ xids[:egid].should == user[:gid]
72
+ xids[:euid].should == user[:uid]
73
+ yielded = true
74
+ end
63
75
 
64
- Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) {}
76
+ xids[:egid].should == 51
77
+ xids[:euid].should == 50
65
78
 
66
- xids.should be_empty
79
+ # It's possible asuser could simply not yield, so the assertions in the
80
+ # block wouldn't fail. So verify those actually got checked.
81
+ yielded.should be_true
82
+ end
83
+
84
+ it "should just yield if user and group are nil" do
85
+ yielded = false
86
+ Puppet::Util::SUIDManager.asuser(nil, nil) { yielded = true }
87
+ yielded.should be_true
88
+ xids.should == {}
89
+ end
90
+
91
+ it "should just change group if only group is given" do
92
+ yielded = false
93
+ Puppet::Util::SUIDManager.asuser(nil, 42) { yielded = true }
94
+ yielded.should be_true
95
+ xids.should == { :egid => 42 }
96
+ end
97
+
98
+ it "should change gid to the primary group of uid by default" do
99
+ Process.stubs(:initgroups)
100
+
101
+ yielded = false
102
+ Puppet::Util::SUIDManager.asuser(42) { yielded = true }
103
+ yielded.should be_true
104
+ xids.should == { :euid => 42, :egid => 42 }
105
+ end
106
+
107
+ it "should change both uid and gid if given" do
108
+ # I don't like the sequence, but it is the only way to assert on the
109
+ # internal behaviour in a reliable fashion, given we need multiple
110
+ # sequenced calls to the same methods. --daniel 2012-02-05
111
+ horror = sequence('of user and group changes')
112
+ Puppet::Util::SUIDManager.expects(:change_group).with(43, false).in_sequence(horror)
113
+ Puppet::Util::SUIDManager.expects(:change_user).with(42, false).in_sequence(horror)
114
+ Puppet::Util::SUIDManager.expects(:change_group).
115
+ with(Puppet::Util::SUIDManager.egid, false).in_sequence(horror)
116
+ Puppet::Util::SUIDManager.expects(:change_user).
117
+ with(Puppet::Util::SUIDManager.euid, false).in_sequence(horror)
118
+
119
+ yielded = false
120
+ Puppet::Util::SUIDManager.asuser(42, 43) { yielded = true }
121
+ yielded.should be_true
122
+ end
67
123
  end
68
124
  end
69
125
 
70
126
  describe "#change_group" do
71
127
  describe "when changing permanently" do
72
- it "should try to change_privilege if it is supported" do
128
+ it "should change_privilege" do
73
129
  Process::GID.expects(:change_privilege).with do |gid|
74
130
  Process.gid = gid
75
131
  Process.egid = gid
@@ -80,15 +136,6 @@ describe Puppet::Util::SUIDManager do
80
136
  xids[:egid].should == 42
81
137
  xids[:gid].should == 42
82
138
  end
83
-
84
- it "should change both egid and gid if change_privilege isn't supported" do
85
- Process::GID.stubs(:change_privilege).raises(NotImplementedError)
86
-
87
- Puppet::Util::SUIDManager.change_group(42, true)
88
-
89
- xids[:egid].should == 42
90
- xids[:gid].should == 42
91
- end
92
139
  end
93
140
 
94
141
  describe "when changing temporarily" do
@@ -103,21 +150,12 @@ describe Puppet::Util::SUIDManager do
103
150
 
104
151
  describe "#change_user" do
105
152
  describe "when changing permanently" do
106
- it "should try to change_privilege if it is supported" do
153
+ it "should change_privilege" do
107
154
  Process::UID.expects(:change_privilege).with do |uid|
108
155
  Process.uid = uid
109
156
  Process.euid = uid
110
157
  end
111
158
 
112
- Puppet::Util::SUIDManager.change_user(42, true)
113
-
114
- xids[:euid].should == 42
115
- xids[:uid].should == 42
116
- end
117
-
118
- it "should change euid and uid and groups if change_privilege isn't supported" do
119
- Process::UID.stubs(:change_privilege).raises(NotImplementedError)
120
-
121
159
  Puppet::Util::SUIDManager.expects(:initgroups).with(42)
122
160
 
123
161
  Puppet::Util::SUIDManager.change_user(42, true)
@@ -129,6 +167,7 @@ describe Puppet::Util::SUIDManager do
129
167
 
130
168
  describe "when changing temporarily" do
131
169
  it "should change only euid and groups" do
170
+ Puppet::Util::SUIDManager.stubs(:initgroups).returns([])
132
171
  Puppet::Util::SUIDManager.change_user(42, false)
133
172
 
134
173
  xids[:euid].should == 42
@@ -165,35 +204,6 @@ describe Puppet::Util::SUIDManager do
165
204
  Kernel.system '' if $CHILD_STATUS.nil?
166
205
  end
167
206
 
168
- describe "with #system" do
169
- it "should set euid/egid when root" do
170
- Process.stubs(:uid).returns(0)
171
- Process.stubs(:egid).returns(51)
172
- Process.stubs(:euid).returns(50)
173
-
174
- Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51)
175
- Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50)
176
-
177
- Puppet::Util::SUIDManager.expects(:change_group).with(user[:uid])
178
- Puppet::Util::SUIDManager.expects(:change_user).with(user[:uid])
179
-
180
- Puppet::Util::SUIDManager.expects(:change_group).with(51)
181
- Puppet::Util::SUIDManager.expects(:change_user).with(50)
182
-
183
- Kernel.expects(:system).with('blah')
184
- Puppet::Util::SUIDManager.system('blah', user[:uid], user[:gid])
185
- end
186
-
187
- it "should not get or set euid/egid when not root" do
188
- Process.stubs(:uid).returns(1)
189
- Kernel.expects(:system).with('blah')
190
-
191
- Puppet::Util::SUIDManager.system('blah', user[:uid], user[:gid])
192
-
193
- xids.should be_empty
194
- end
195
- end
196
-
197
207
  describe "with #run_and_capture" do
198
208
  it "should capture the output and return process status" do
199
209
  Puppet::Util.
@@ -36,29 +36,31 @@ describe "Pure ruby yaml implementation" do
36
36
  end
37
37
  }
38
38
 
39
- def set_of_lines(l)
40
- l.split("\n").sort
41
- end
42
-
43
39
  it "should handle references to Array in Hash values correctly" do
44
40
  list = [1]
45
41
  data = { "one" => list, "two" => list }
46
- data.to_yaml.should == "--- \n two: &id001 \n - 1\n one: *id001"
42
+ data.to_yaml.should =~ / two: [&*]id001/
43
+ data.to_yaml.should =~ / one: [&*]id001/
47
44
  expect { YAML.load(data.to_yaml).should == data }.should_not raise_error
48
45
  end
49
46
 
50
47
  it "should handle references to Hash in Hash values correctly" do
51
48
  hash = { 1 => 1 }
52
49
  data = { "one" => hash, "two" => hash }
53
- # This could still someday fail because the order change would also change which one got the back ref
54
- set_of_lines(data.to_yaml).should == set_of_lines("--- \n two: &id001 \n 1: 1\n one: *id001")
50
+ lines = data.to_yaml.split("\n")
51
+ lines.should be_any {|x| x =~ /--- / }
52
+ lines.should be_any {|x| x =~ / one: [*&]id001/ }
53
+ lines.should be_any {|x| x =~ / two: [*&]id001/ }
55
54
  expect { YAML.load(data.to_yaml).should == data }.should_not raise_error
56
55
  end
57
56
 
58
57
  it "should handle references to Scalar in Hash" do
59
58
  str = "hello"
60
59
  data = { "one" => str, "two" => str }
61
- set_of_lines(data.to_yaml).should == set_of_lines("--- \n two: hello\n one: hello")
60
+ lines = data.to_yaml.split("\n")
61
+ lines.should be_any {|x| x =~ /--- / }
62
+ lines.should be_any {|x| x =~ / one: hello/ }
63
+ lines.should be_any {|x| x =~ / two: hello/ }
62
64
  expect { YAML.load(data.to_yaml).should == data }.should_not raise_error
63
65
  end
64
66
 
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env rspec
2
+ require 'spec_helper'
3
+
4
+ describe Puppet::Util do
5
+ subject { Puppet::Util }
6
+ include PuppetSpec::Files
7
+
8
+ context "#replace_file" do
9
+ it { should respond_to :replace_file }
10
+
11
+ let :target do
12
+ target = Tempfile.new("puppet-util-replace-file")
13
+ target.puts("hello, world")
14
+ target.flush # make sure content is on disk.
15
+ target.fsync rescue nil
16
+ target.close
17
+ target
18
+ end
19
+
20
+ it "should fail if no block is given" do
21
+ expect { subject.replace_file(target.path, 0600) }.to raise_error /block/
22
+ end
23
+
24
+ it "should replace a file when invoked" do
25
+ # Check that our file has the expected content.
26
+ File.read(target.path).should == "hello, world\n"
27
+
28
+ # Replace the file.
29
+ subject.replace_file(target.path, 0600) do |fh|
30
+ fh.puts "I am the passenger..."
31
+ end
32
+
33
+ # ...and check the replacement was complete.
34
+ File.read(target.path).should == "I am the passenger...\n"
35
+ end
36
+
37
+ [0555, 0600, 0660, 0700, 0770].each do |mode|
38
+ it "should copy 0#{mode.to_s(8)} permissions from the target file by default" do
39
+ File.chmod(mode, target.path)
40
+
41
+ (File.stat(target.path).mode & 07777).should == mode
42
+
43
+ subject.replace_file(target.path, 0000) {|fh| fh.puts "bazam" }
44
+
45
+ (File.stat(target.path).mode & 07777).should == mode
46
+ File.read(target.path).should == "bazam\n"
47
+ end
48
+ end
49
+
50
+ it "should copy the permissions of the source file before yielding" do
51
+ File.chmod(0555, target.path)
52
+ inode = File.stat(target.path).ino
53
+
54
+ yielded = false
55
+ subject.replace_file(target.path, 0600) do |fh|
56
+ (File.stat(fh.path).mode & 07777).should == 0555
57
+ yielded = true
58
+ end
59
+ yielded.should be_true
60
+
61
+ # We can't check inode on Windows
62
+ File.stat(target.path).ino.should_not == inode
63
+
64
+ (File.stat(target.path).mode & 07777).should == 0555
65
+ end
66
+
67
+ it "should use the default permissions if the source file doesn't exist" do
68
+ new_target = target.path + '.foo'
69
+ File.should_not be_exist(new_target)
70
+
71
+ begin
72
+ subject.replace_file(new_target, 0555) {|fh| fh.puts "foo" }
73
+ (File.stat(new_target).mode & 07777).should == 0555
74
+ ensure
75
+ File.unlink(new_target) if File.exists?(new_target)
76
+ end
77
+ end
78
+
79
+ it "should not replace the file if an exception is thrown in the block" do
80
+ yielded = false
81
+ threw = false
82
+
83
+ begin
84
+ subject.replace_file(target.path, 0600) do |fh|
85
+ yielded = true
86
+ fh.puts "different content written, then..."
87
+ raise "...throw some random failure"
88
+ end
89
+ rescue Exception => e
90
+ if e.to_s =~ /some random failure/
91
+ threw = true
92
+ else
93
+ raise
94
+ end
95
+ end
96
+
97
+ yielded.should be_true
98
+ threw.should be_true
99
+
100
+ # ...and check the replacement was complete.
101
+ File.read(target.path).should == "hello, world\n"
102
+ end
103
+ end
104
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puppet
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
8
  - 6
9
- - 13
10
- version: 2.6.13
9
+ - 14
10
+ version: 2.6.14
11
11
  platform: ruby
12
12
  authors:
13
13
  - Puppet Labs
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-12-12 00:00:00 Z
18
+ date: 2012-02-22 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: facter
@@ -1296,6 +1296,7 @@ files:
1296
1296
  - spec/unit/type/file_spec.rb
1297
1297
  - spec/unit/type/filebucket_spec.rb
1298
1298
  - spec/unit/type/group_spec.rb
1299
+ - spec/unit/type/k5login_spec.rb
1299
1300
  - spec/unit/type/macauthorization_spec.rb
1300
1301
  - spec/unit/type/maillist_spec.rb
1301
1302
  - spec/unit/type/mcx_spec.rb
@@ -1361,6 +1362,7 @@ files:
1361
1362
  - spec/unit/util/user_attr_spec.rb
1362
1363
  - spec/unit/util/warnings_spec.rb
1363
1364
  - spec/unit/util/zaml_spec.rb
1365
+ - spec/unit/util_spec.rb
1364
1366
  - bin/puppetca
1365
1367
  - bin/puppetd
1366
1368
  - bin/puppetmasterd