puppet 2.7.5 → 2.7.6

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.

Files changed (140) hide show
  1. data/CHANGELOG +121 -0
  2. data/conf/redhat/puppet.spec +16 -7
  3. data/lib/puppet.rb +1 -1
  4. data/lib/puppet/application/cert.rb +17 -3
  5. data/lib/puppet/application/device.rb +1 -0
  6. data/lib/puppet/application/kick.rb +0 -2
  7. data/lib/puppet/application/resource.rb +73 -66
  8. data/lib/puppet/configurer/plugin_handler.rb +6 -2
  9. data/lib/puppet/defaults.rb +60 -5
  10. data/lib/puppet/face/ca.rb +11 -2
  11. data/lib/puppet/face/certificate.rb +33 -4
  12. data/lib/puppet/file_serving/fileset.rb +1 -1
  13. data/lib/puppet/file_serving/indirection_hooks.rb +2 -2
  14. data/lib/puppet/file_serving/metadata.rb +43 -4
  15. data/lib/puppet/indirector.rb +0 -1
  16. data/lib/puppet/indirector/request.rb +3 -4
  17. data/lib/puppet/indirector/resource/active_record.rb +3 -10
  18. data/lib/puppet/indirector/resource/ral.rb +2 -2
  19. data/lib/puppet/indirector/rest.rb +1 -1
  20. data/lib/puppet/network/handler/ca.rb +16 -106
  21. data/lib/puppet/network/handler/master.rb +0 -3
  22. data/lib/puppet/network/handler/runner.rb +1 -0
  23. data/lib/puppet/parser/scope.rb +10 -0
  24. data/lib/puppet/provider/file/posix.rb +72 -34
  25. data/lib/puppet/provider/file/windows.rb +100 -0
  26. data/lib/puppet/provider/group/windows_adsi.rb +2 -2
  27. data/lib/puppet/provider/user/windows_adsi.rb +19 -4
  28. data/lib/puppet/resource.rb +16 -0
  29. data/lib/puppet/resource/catalog.rb +1 -1
  30. data/lib/puppet/ssl/certificate.rb +2 -2
  31. data/lib/puppet/ssl/certificate_authority.rb +86 -10
  32. data/lib/puppet/ssl/certificate_authority/interface.rb +64 -19
  33. data/lib/puppet/ssl/certificate_factory.rb +112 -91
  34. data/lib/puppet/ssl/certificate_request.rb +88 -1
  35. data/lib/puppet/ssl/host.rb +20 -3
  36. data/lib/puppet/type/file.rb +15 -34
  37. data/lib/puppet/type/file/group.rb +11 -91
  38. data/lib/puppet/type/file/mode.rb +11 -41
  39. data/lib/puppet/type/file/owner.rb +18 -34
  40. data/lib/puppet/type/file/source.rb +22 -7
  41. data/lib/puppet/type/group.rb +4 -3
  42. data/lib/puppet/type/user.rb +4 -1
  43. data/lib/puppet/util.rb +59 -6
  44. data/lib/puppet/util/adsi.rb +11 -0
  45. data/lib/puppet/util/log.rb +4 -0
  46. data/lib/puppet/util/log/destinations.rb +7 -1
  47. data/lib/puppet/util/monkey_patches.rb +19 -0
  48. data/lib/puppet/util/network_device/config.rb +4 -5
  49. data/lib/puppet/util/settings.rb +5 -0
  50. data/lib/puppet/util/suidmanager.rb +0 -1
  51. data/lib/puppet/util/windows.rb +4 -0
  52. data/lib/puppet/util/windows/error.rb +16 -0
  53. data/lib/puppet/util/windows/security.rb +593 -0
  54. data/spec/integration/defaults_spec.rb +27 -0
  55. data/spec/integration/network/handler_spec.rb +1 -1
  56. data/spec/integration/type/file_spec.rb +382 -145
  57. data/spec/integration/util/windows/security_spec.rb +468 -0
  58. data/spec/shared_behaviours/file_serving.rb +4 -3
  59. data/spec/unit/application/agent_spec.rb +1 -0
  60. data/spec/unit/application/device_spec.rb +5 -0
  61. data/spec/unit/application/resource_spec.rb +62 -101
  62. data/spec/unit/configurer/downloader_spec.rb +2 -2
  63. data/spec/unit/configurer/plugin_handler_spec.rb +15 -8
  64. data/spec/unit/configurer_spec.rb +2 -2
  65. data/spec/unit/face/ca_spec.rb +34 -0
  66. data/spec/unit/face/certificate_spec.rb +168 -1
  67. data/spec/unit/file_serving/fileset_spec.rb +1 -1
  68. data/spec/unit/file_serving/indirection_hooks_spec.rb +1 -1
  69. data/spec/unit/file_serving/metadata_spec.rb +151 -107
  70. data/spec/unit/indirector/certificate_request/ca_spec.rb +0 -3
  71. data/spec/unit/indirector/direct_file_server_spec.rb +10 -9
  72. data/spec/unit/indirector/file_metadata/file_spec.rb +6 -4
  73. data/spec/unit/indirector/request_spec.rb +13 -3
  74. data/spec/unit/indirector/resource/active_record_spec.rb +4 -10
  75. data/spec/unit/indirector/resource/ral_spec.rb +6 -4
  76. data/spec/unit/indirector/rest_spec.rb +5 -6
  77. data/spec/unit/network/handler/ca_spec.rb +86 -0
  78. data/spec/unit/parser/collector_spec.rb +7 -7
  79. data/spec/unit/parser/scope_spec.rb +20 -0
  80. data/spec/unit/provider/file/posix_spec.rb +226 -0
  81. data/spec/unit/provider/file/windows_spec.rb +136 -0
  82. data/spec/unit/provider/group/windows_adsi_spec.rb +7 -2
  83. data/spec/unit/provider/user/windows_adsi_spec.rb +36 -3
  84. data/spec/unit/resource/catalog_spec.rb +20 -10
  85. data/spec/unit/resource_spec.rb +55 -8
  86. data/spec/unit/ssl/certificate_authority/interface_spec.rb +97 -54
  87. data/spec/unit/ssl/certificate_authority_spec.rb +133 -23
  88. data/spec/unit/ssl/certificate_factory_spec.rb +90 -70
  89. data/spec/unit/ssl/certificate_request_spec.rb +62 -1
  90. data/spec/unit/ssl/certificate_spec.rb +20 -14
  91. data/spec/unit/ssl/host_spec.rb +52 -6
  92. data/spec/unit/type/file/content_spec.rb +4 -4
  93. data/spec/unit/type/file/group_spec.rb +34 -96
  94. data/spec/unit/type/file/mode_spec.rb +88 -0
  95. data/spec/unit/type/file/owner_spec.rb +32 -123
  96. data/spec/unit/type/file/source_spec.rb +120 -41
  97. data/spec/unit/type/file_spec.rb +1033 -753
  98. data/spec/unit/type_spec.rb +19 -1
  99. data/spec/unit/util/adsi_spec.rb +19 -0
  100. data/spec/unit/util/log/destinations_spec.rb +75 -0
  101. data/spec/unit/util/log_spec.rb +15 -0
  102. data/spec/unit/util/network_device/config_spec.rb +7 -0
  103. data/spec/unit/util/settings_spec.rb +10 -0
  104. data/spec/unit/util_spec.rb +126 -13
  105. data/test/language/functions.rb +0 -1
  106. data/test/language/snippets.rb +0 -9
  107. data/test/lib/puppettest/exetest.rb +1 -1
  108. data/test/lib/puppettest/servertest.rb +0 -1
  109. data/test/rails/rails.rb +0 -1
  110. data/test/ral/type/filesources.rb +0 -60
  111. metadata +13 -33
  112. data/lib/puppet/network/client.rb +0 -174
  113. data/lib/puppet/network/client/ca.rb +0 -56
  114. data/lib/puppet/network/client/file.rb +0 -6
  115. data/lib/puppet/network/client/proxy.rb +0 -27
  116. data/lib/puppet/network/client/report.rb +0 -26
  117. data/lib/puppet/network/client/runner.rb +0 -10
  118. data/lib/puppet/network/client/status.rb +0 -4
  119. data/lib/puppet/network/http_server.rb +0 -3
  120. data/lib/puppet/network/http_server/mongrel.rb +0 -130
  121. data/lib/puppet/network/http_server/webrick.rb +0 -155
  122. data/lib/puppet/network/xmlrpc/client.rb +0 -211
  123. data/lib/puppet/provider/file/win32.rb +0 -72
  124. data/lib/puppet/sslcertificates.rb +0 -146
  125. data/lib/puppet/sslcertificates/ca.rb +0 -375
  126. data/lib/puppet/sslcertificates/certificate.rb +0 -255
  127. data/lib/puppet/sslcertificates/inventory.rb +0 -38
  128. data/lib/puppet/sslcertificates/support.rb +0 -146
  129. data/spec/integration/network/client_spec.rb +0 -18
  130. data/spec/unit/network/xmlrpc/client_spec.rb +0 -172
  131. data/spec/unit/sslcertificates/ca_spec.rb +0 -106
  132. data/test/certmgr/certmgr.rb +0 -308
  133. data/test/certmgr/inventory.rb +0 -69
  134. data/test/certmgr/support.rb +0 -105
  135. data/test/network/client/ca.rb +0 -69
  136. data/test/network/client/dipper.rb +0 -34
  137. data/test/network/handler/ca.rb +0 -273
  138. data/test/network/server/mongrel_test.rb +0 -99
  139. data/test/network/server/webrick.rb +0 -111
  140. data/test/network/xmlrpc/client.rb +0 -45
@@ -37,7 +37,10 @@ module Puppet
37
37
  desc "The group ID. Must be specified numerically. If not
38
38
  specified, a number will be picked, which can result in ID
39
39
  differences across systems and thus is not recommended. The
40
- GID is picked according to local system standards."
40
+ GID is picked according to local system standards.
41
+
42
+ On Windows, the property will return the group's security
43
+ identifier (SID)."
41
44
 
42
45
  def retrieve
43
46
  provider.gid
@@ -104,8 +107,6 @@ module Puppet
104
107
 
105
108
  newparam(:ia_load_module, :required_features => :manages_aix_lam) do
106
109
  desc "The name of the I&A module to use to manage this user"
107
-
108
- defaultto "compat"
109
110
  end
110
111
 
111
112
  newproperty(:attributes, :parent => Puppet::Property::KeyValue, :required_features => :manages_aix_lam) do
@@ -93,7 +93,10 @@ module Puppet
93
93
  recommended. This is especially noteworthy if you use Puppet
94
94
  to manage the same user on both Darwin and other platforms,
95
95
  since Puppet does the ID generation for you on Darwin, but the
96
- tools do so on other platforms."
96
+ tools do so on other platforms.
97
+
98
+ On Windows, the property will return the user's security
99
+ identifier (SID)."
97
100
 
98
101
  munge do |value|
99
102
  case value
@@ -7,6 +7,7 @@ require 'tempfile'
7
7
  require 'puppet/external/lock'
8
8
  require 'monitor'
9
9
  require 'puppet/util/execution_stub'
10
+ require 'uri'
10
11
 
11
12
  module Puppet
12
13
  # A command failed to execute.
@@ -205,15 +206,61 @@ module Util
205
206
  slash = '[\\\\/]'
206
207
  name = '[^\\\\/]+'
207
208
  regexes = {
208
- :windows => %r!^([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name})!i,
209
+ :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i,
209
210
  :posix => %r!^/!,
210
211
  }
212
+ require 'puppet'
211
213
  platform ||= Puppet.features.microsoft_windows? ? :windows : :posix
212
214
 
213
215
  !! (path =~ regexes[platform])
214
216
  end
215
217
  module_function :absolute_path?
216
218
 
219
+ # Convert a path to a file URI
220
+ def path_to_uri(path)
221
+ return unless path
222
+
223
+ params = { :scheme => 'file' }
224
+
225
+ if Puppet.features.microsoft_windows?
226
+ path = path.gsub(/\\/, '/')
227
+
228
+ if unc = /^\/\/([^\/]+)(\/[^\/]+)/.match(path)
229
+ params[:host] = unc[1]
230
+ path = unc[2]
231
+ elsif path =~ /^[a-z]:\//i
232
+ path = '/' + path
233
+ end
234
+ end
235
+
236
+ params[:path] = URI.escape(path)
237
+
238
+ begin
239
+ URI::Generic.build(params)
240
+ rescue => detail
241
+ raise Puppet::Error, "Failed to convert '#{path}' to URI: #{detail}"
242
+ end
243
+ end
244
+ module_function :path_to_uri
245
+
246
+ # Get the path component of a URI
247
+ def uri_to_path(uri)
248
+ return unless uri.is_a?(URI)
249
+
250
+ path = URI.unescape(uri.path)
251
+
252
+ if Puppet.features.microsoft_windows? and uri.scheme == 'file'
253
+ if uri.host
254
+ path = "//#{uri.host}" + path # UNC
255
+ else
256
+ path.sub!(/^\//, '')
257
+ end
258
+ end
259
+
260
+ path
261
+ end
262
+ module_function :uri_to_path
263
+
217
264
  # Execute the provided command in a pipe, yielding the pipe object.
218
265
  def execpipe(command, failonfail = true)
219
266
  if respond_to? :debug
@@ -222,7 +269,8 @@ module Util
222
269
  Puppet.debug "Executing '#{command}'"
223
270
  end
224
271
 
225
- output = open("| #{command} 2>&1") do |pipe|
272
+ command_str = command.respond_to?(:join) ? command.join('') : command
273
+ output = open("| #{command_str} 2>&1") do |pipe|
226
274
  yield pipe
227
275
  end
228
276
 
@@ -244,7 +292,12 @@ module Util
244
292
 
245
293
  def execute_posix(command, arguments, stdin, stdout, stderr)
246
294
  child_pid = Kernel.fork do
247
- command = Array(command)
295
+ # We can't just call Array(command), and rely on it returning
296
+ # things like ['foo'], when passed ['foo'], because
297
+ # Array(command) will call command.to_a internally, which when
298
+ # given a string can end up doing Very Bad Things(TM), such as
299
+ # turning "/tmp/foo;\r\n /bin/echo" into ["/tmp/foo;\r\n", " /bin/echo"]
300
+ command = [command].flatten
248
301
  Process.setsid
249
302
  begin
250
303
  $stdin.reopen(stdin)
@@ -309,12 +362,12 @@ module Util
309
362
  return execution_stub.call(*exec_args)
310
363
  elsif Puppet.features.posix?
311
364
  child_pid = execute_posix(*exec_args)
365
+ child_status = Process.waitpid2(child_pid).last.exitstatus
312
366
  elsif Puppet.features.microsoft_windows?
313
367
  child_pid = execute_windows(*exec_args)
368
+ child_status = Process.waitpid2(child_pid).last
314
369
  end
315
370
 
316
- child_status = Process.waitpid2(child_pid).last
317
-
318
371
  [stdin, stdout, stderr].each {|io| io.close rescue nil}
319
372
 
320
373
  # read output in if required
@@ -324,7 +377,7 @@ module Util
324
377
  end
325
378
 
326
379
  if arguments[:failonfail] and child_status != 0
327
- raise ExecutionFailure, "Execution of '#{str}' returned #{child_status.exitstatus}: #{output}"
380
+ raise ExecutionFailure, "Execution of '#{str}' returned #{child_status}: #{output}"
328
381
  end
329
382
 
330
383
  output
@@ -48,6 +48,17 @@ module Puppet::Util::ADSI
48
48
  def execquery(query)
49
49
  connect(wmi_resource_uri).execquery(query)
50
50
  end
51
+
52
+ def sid_for_account(name)
53
+ sid = nil
54
+
55
+ execquery(
56
+ "SELECT Sid from Win32_Account
57
+ WHERE Name = '#{name}' AND LocalAccount = true"
58
+ ).each {|u| sid ||= u.Sid}
59
+
60
+ sid
61
+ end
51
62
  end
52
63
 
53
64
  class User
@@ -120,6 +120,10 @@ class Puppet::Util::Log
120
120
  klass.match?(dest)
121
121
  end
122
122
 
123
+ if type.respond_to?(:suitable?) and not type.suitable?(dest)
124
+ return
125
+ end
126
+
123
127
  raise Puppet::DevError, "Unknown destination type #{dest}" unless type
124
128
 
125
129
  begin
@@ -1,4 +1,8 @@
1
1
  Puppet::Util::Log.newdesttype :syslog do
2
+ def self.suitable?(obj)
3
+ Puppet.features.syslog?
4
+ end
5
+
2
6
  def close
3
7
  Syslog.close
4
8
  end
@@ -37,7 +41,9 @@ Puppet::Util::Log.newdesttype :syslog do
37
41
  end
38
42
 
39
43
  Puppet::Util::Log.newdesttype :file do
40
- match(/^\//)
44
+ def self.match?(obj)
45
+ Puppet::Util.absolute_path?(obj)
46
+ end
41
47
 
42
48
  def close
43
49
  if defined?(@file)
@@ -103,6 +103,8 @@ class Array
103
103
  ret += tmp.combination(num - 1).map{|a| a.unshift(e) }
104
104
  end
105
105
  end unless method_defined? :combination
106
+
107
+ alias :count :length unless method_defined? :count
106
108
  end
107
109
 
108
110
 
@@ -111,3 +113,20 @@ class Symbol
111
113
  Proc.new { |*args| args.shift.__send__(self, *args) }
112
114
  end unless method_defined? :to_proc
113
115
  end
116
+
117
+
118
+ class String
119
+ def lines(separator = $/)
120
+ lines = split(separator)
121
+ block_given? and lines.each {|line| yield line }
122
+ lines
123
+ end
124
+ end
125
+
126
+ class IO
127
+ def lines(separator = $/)
128
+ lines = split(separator)
129
+ block_given? and lines.each {|line| yield line }
130
+ lines
131
+ end
132
+ end
@@ -51,10 +51,10 @@ class Puppet::Util::NetworkDevice::Config < Puppet::Util::LoadedFile
51
51
  when /^\s*$/ # skip blank lines
52
52
  count += 1
53
53
  next
54
- when /^\[([\w.]+)\]\s*$/ # [device.fqdn]
54
+ when /^\[([\w.-]+)\]\s*$/ # [device.fqdn]
55
55
  name = $1
56
56
  name.chomp!
57
- raise ConfigurationError, "Duplicate device found at line #{count}, already found at #{device.line}" if devices.include?(name)
57
+ raise Puppet::Error, "Duplicate device found at line #{count}, already found at #{device.line}" if devices.include?(name)
58
58
  device = OpenStruct.new
59
59
  device.name = name
60
60
  device.line = count
@@ -63,7 +63,7 @@ class Puppet::Util::NetworkDevice::Config < Puppet::Util::LoadedFile
63
63
  when /^\s*(type|url)\s+(.+)$/
64
64
  parse_directive(device, $1, $2, count)
65
65
  else
66
- raise ConfigurationError, "Invalid line #{count}: #{line}"
66
+ raise Puppet::Error, "Invalid line #{count}: #{line}"
67
67
  end
68
68
  count += 1
69
69
  }
@@ -85,8 +85,7 @@ class Puppet::Util::NetworkDevice::Config < Puppet::Util::LoadedFile
85
85
  when "url"
86
86
  device.url = value
87
87
  else
88
- raise ConfigurationError,
89
- "Invalid argument '#{var}' at line #{count}"
88
+ raise Puppet::Error, "Invalid argument '#{var}' at line #{count}"
90
89
  end
91
90
  end
92
91
 
@@ -492,6 +492,11 @@ class Puppet::Util::Settings
492
492
  end
493
493
  type = legacy_to_mode(type, param)
494
494
  @sync.synchronize do # yay, thread-safe
495
+ # Allow later inspection to determine if the setting was set on the
496
+ # command line, or through some other code path. Used for the
497
+ # `dns_alt_names` option during cert generate. --daniel 2011-10-18
498
+ setting.setbycli = true if type == :cli
499
+
495
500
  @values[type][param] = value
496
501
  @cache.clear
497
502
 
@@ -150,4 +150,3 @@ module Puppet::Util::SUIDManager
150
150
  end
151
151
  module_function :system
152
152
  end
153
-
@@ -0,0 +1,4 @@
1
+ module Puppet::Util::Windows
2
+ require 'puppet/util/windows/error'
3
+ require 'puppet/util/windows/security'
4
+ end
@@ -0,0 +1,16 @@
1
+ require 'puppet/util/windows'
2
+
3
+ # represents an error resulting from a Win32 error code
4
+ class Puppet::Util::Windows::Error < Puppet::Error
5
+ require 'windows/error'
6
+ include Windows::Error
7
+
8
+ attr_reader :code
9
+
10
+ def initialize(message, code = GetLastError.call)
11
+ super(message + ": #{get_last_error(code)}")
12
+
13
+ @code = code
14
+ end
15
+ end
16
+
@@ -0,0 +1,593 @@
1
+ # This class maps POSIX owner, group, and modes to the Windows
2
+ # security model, and back.
3
+ #
4
+ # The primary goal of this mapping is to ensure that owner, group, and
5
+ # modes can be round-tripped in a consistent and deterministic
6
+ # way. Otherwise, Puppet might think file resources are out-of-sync
7
+ # every time it runs. A secondary goal is to provide equivalent
8
+ # permissions for common use-cases. For example, setting the owner to
9
+ # "Administrators", group to "Users", and mode to 750 (which also
10
+ # denies access to everyone else.
11
+ #
12
+ # There are some well-known problems mapping windows and POSIX
13
+ # permissions due to differences between the two security
14
+ # models. Search for "POSIX permission mapping leak". In POSIX, access
15
+ # to a file is determined solely based on the most specific class
16
+ # (user, group, other). So a mode of 460 would deny write access to
17
+ # the owner even if they are a member of the group. But in Windows,
18
+ # the entire access control list is walked until the user is
19
+ # explicitly denied or allowed (denied take precedence, and if neither
20
+ # occurs they are denied). As a result, a user could be allowed access
21
+ # based on their group membership. To solve this problem, other people
22
+ # have used deny access control entries to more closely model POSIX,
23
+ # but this introduces a lot of complexity.
24
+ #
25
+ # In general, this implementation only supports "typical" permissions,
26
+ # where group permissions are a subset of user, and other permissions
27
+ # are a subset of group, e.g. 754, but not 467. However, there are
28
+ # some Windows quirks to be aware of.
29
+ #
30
+ # * The owner can be either a user or group SID, and most system files
31
+ # are owned by the Administrators group.
32
+ # * The group can be either a user or group SID.
33
+ # * Unexpected results can occur if the owner and group are the
34
+ # same, but the user and group classes are different, e.g. 750. In
35
+ # this case, it is not possible to allow write access to the owner,
36
+ # but not the group. As a result, the actual permissions set on the
37
+ # file would be 770.
38
+ # * In general, only privileged users can set the owner, group, or
39
+ # change the mode for files they do not own. In 2003, the user must
40
+ # be a member of the Administrators group. In Vista/2008, the user
41
+ # must be running with elevated privileges.
42
+ # * A file/dir can be deleted by anyone with the DELETE access right
43
+ # OR by anyone that has the FILE_DELETE_CHILD access right for the
44
+ # parent. See http://support.microsoft.com/kb/238018. But on Unix,
45
+ # the user must have write access to the file/dir AND execute access
46
+ # to all of the parent path components.
47
+ # * Many access control entries are inherited from parent directories,
48
+ # and it is common for file/dirs to have more than 3 entries,
49
+ # e.g. Users, Power Users, Administrators, SYSTEM, etc, which cannot
50
+ # be mapped into the 3 class POSIX model. The get_mode method will
51
+ # set the S_IEXTRA bit flag indicating that an access control entry
52
+ # was found whose SID is neither the owner, group, or other. This
53
+ # enables Puppet to detect when file/dirs are out-of-sync,
54
+ # especially those that Puppet did not create, but is attempting
55
+ # to manage.
56
+ # * On Unix, the owner and group can be modified without changing the
57
+ # mode. But on Windows, an access control entry specifies which SID
58
+ # it applies to. As a result, the set_owner and set_group methods
59
+ # automatically rebuild the access control list based on the new
60
+ # (and different) owner or group.
61
+
62
+ require 'puppet/util/windows'
63
+
64
+ require 'win32/security'
65
+
66
+ require 'windows/file'
67
+ require 'windows/handle'
68
+ require 'windows/security'
69
+ require 'windows/process'
70
+ require 'windows/memory'
71
+
72
+ module Puppet::Util::Windows::Security
73
+ include Windows::File
74
+ include Windows::Handle
75
+ include Windows::Security
76
+ include Windows::Process
77
+ include Windows::Memory
78
+ include Windows::MSVCRT::Buffer
79
+
80
+ extend Puppet::Util::Windows::Security
81
+
82
+ # file modes
83
+ S_IRUSR = 0000400
84
+ S_IRGRP = 0000040
85
+ S_IROTH = 0000004
86
+ S_IWUSR = 0000200
87
+ S_IWGRP = 0000020
88
+ S_IWOTH = 0000002
89
+ S_IXUSR = 0000100
90
+ S_IXGRP = 0000010
91
+ S_IXOTH = 0000001
92
+ S_IRWXU = 0000700
93
+ S_IRWXG = 0000070
94
+ S_IRWXO = 0000007
95
+ S_IEXTRA = 02000000 # represents an extra ace
96
+
97
+ # constants that are missing from Windows::Security
98
+ PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000
99
+ UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000
100
+ NO_INHERITANCE = 0x0
101
+
102
+ # Set the owner of the object referenced by +path+ to the specified
103
+ # +owner_sid+. The owner sid should be of the form "S-1-5-32-544"
104
+ # and can either be a user or group. Only a user with the
105
+ # SE_RESTORE_NAME privilege in their process token can overwrite the
106
+ # object's owner to something other than the current user.
107
+ def set_owner(owner_sid, path)
108
+ old_sid = get_owner(path)
109
+
110
+ change_sid(old_sid, owner_sid, OWNER_SECURITY_INFORMATION, path)
111
+ end
112
+
113
+ # Get the owner of the object referenced by +path+. The returned
114
+ # value is a SID string, e.g. "S-1-5-32-544". Any user with read
115
+ # access to an object can get the owner. Only a user with the
116
+ # SE_BACKUP_NAME privilege in their process token can get the owner
117
+ # for objects they do not have read access to.
118
+ def get_owner(path)
119
+ get_sid(OWNER_SECURITY_INFORMATION, path)
120
+ end
121
+
122
+ # Set the owner of the object referenced by +path+ to the specified
123
+ # +group_sid+. The group sid should be of the form "S-1-5-32-544"
124
+ # and can either be a user or group. Any user with WRITE_OWNER
125
+ # access to the object can change the group (regardless of whether
126
+ # the current user belongs to that group or not).
127
+ def set_group(group_sid, path)
128
+ old_sid = get_group(path)
129
+
130
+ change_sid(old_sid, group_sid, GROUP_SECURITY_INFORMATION, path)
131
+ end
132
+
133
+ # Get the group of the object referenced by +path+. The returned
134
+ # value is a SID string, e.g. "S-1-5-32-544". Any user with read
135
+ # access to an object can get the group. Only a user with the
136
+ # SE_BACKUP_NAME privilege in their process token can get the group
137
+ # for objects they do not have read access to.
138
+ def get_group(path)
139
+ get_sid(GROUP_SECURITY_INFORMATION, path)
140
+ end
141
+
142
+ def change_sid(old_sid, new_sid, info, path)
143
+ if old_sid != new_sid
144
+ mode = get_mode(path)
145
+
146
+ string_to_sid_ptr(new_sid) do |psid|
147
+ with_privilege(SE_RESTORE_NAME) do
148
+ open_file(path, WRITE_OWNER) do |handle|
149
+ set_security_info(handle, info, psid)
150
+ end
151
+ end
152
+ end
153
+
154
+ # rebuild dacl now that sid has changed
155
+ set_mode(mode, path)
156
+ end
157
+ end
158
+
159
+ def get_sid(info, path)
160
+ with_privilege(SE_BACKUP_NAME) do
161
+ open_file(path, READ_CONTROL) do |handle|
162
+ get_security_info(handle, info)
163
+ end
164
+ end
165
+ end
166
+
167
+ def get_attributes(path)
168
+ attributes = GetFileAttributes(path)
169
+
170
+ raise Puppet::Util::Windows::Error.new("Failed to get file attributes") if attributes == INVALID_FILE_ATTRIBUTES
171
+
172
+ attributes
173
+ end
174
+
175
+ def add_attributes(path, flags)
176
+ set_attributes(path, get_attributes(path) | flags)
177
+ end
178
+
179
+ def remove_attributes(path, flags)
180
+ set_attributes(path, get_attributes(path) & ~flags)
181
+ end
182
+
183
+ def set_attributes(path, flags)
184
+ raise Puppet::Util::Windows::Error.new("Failed to set file attributes") if SetFileAttributes(path, flags) == 0
185
+ end
186
+
187
+ MASK_TO_MODE = {
188
+ FILE_GENERIC_READ => S_IROTH,
189
+ FILE_GENERIC_WRITE => S_IWOTH,
190
+ (FILE_GENERIC_EXECUTE & ~FILE_READ_ATTRIBUTES) => S_IXOTH
191
+ }
192
+
193
+ # Get the mode of the object referenced by +path+. The returned
194
+ # integer value represents the POSIX-style read, write, and execute
195
+ # modes for the user, group, and other classes, e.g. 0640. Other
196
+ # modes, e.g. S_ISVTX, are not supported. Any user with read access
197
+ # to an object can get the mode. Only a user with the SE_BACKUP_NAME
198
+ # privilege in their process token can get the mode for objects they
199
+ # do not have read access to.
200
+ def get_mode(path)
201
+ owner_sid = get_owner(path)
202
+ group_sid = get_group(path)
203
+ well_known_world_sid = Win32::Security::SID::Everyone
204
+
205
+ with_privilege(SE_BACKUP_NAME) do
206
+ open_file(path, READ_CONTROL) do |handle|
207
+ mode = 0
208
+
209
+ get_dacl(handle).each do |ace|
210
+ case ace[:sid]
211
+ when owner_sid
212
+ MASK_TO_MODE.each_pair do |k,v|
213
+ if (ace[:mask] & k) == k
214
+ mode |= (v << 6)
215
+ end
216
+ end
217
+ when group_sid
218
+ MASK_TO_MODE.each_pair do |k,v|
219
+ if (ace[:mask] & k) == k
220
+ mode |= (v << 3)
221
+ end
222
+ end
223
+ when well_known_world_sid
224
+ MASK_TO_MODE.each_pair do |k,v|
225
+ if (ace[:mask] & k) == k
226
+ mode |= (v << 6) | (v << 3) | v
227
+ end
228
+ end
229
+ else
230
+ #puts "Warning, unable to map SID into POSIX mode: #{ace[:sid]}"
231
+ mode |= S_IEXTRA
232
+ end
233
+
234
+ # if owner and group the same, then user and group modes are the OR of both
235
+ if owner_sid == group_sid
236
+ mode |= ((mode & S_IRWXG) << 3) | ((mode & S_IRWXU) >> 3)
237
+ #puts "owner: #{group_sid}, 0x#{ace[:mask].to_s(16)}, #{mode.to_s(8)}"
238
+ end
239
+ end
240
+
241
+ #puts "get_mode: #{mode.to_s(8)}"
242
+ mode
243
+ end
244
+ end
245
+ end
246
+
247
+ MODE_TO_MASK = {
248
+ S_IROTH => FILE_GENERIC_READ,
249
+ S_IWOTH => FILE_GENERIC_WRITE,
250
+ S_IXOTH => (FILE_GENERIC_EXECUTE & ~FILE_READ_ATTRIBUTES),
251
+ (S_IWOTH | S_IXOTH) => FILE_DELETE_CHILD,
252
+ }
253
+
254
+ # Set the mode of the object referenced by +path+ to the specified
255
+ # +mode+. The mode should be specified as POSIX-stye read, write,
256
+ # and execute modes for the user, group, and other classes,
257
+ # e.g. 0640. Other modes, e.g. S_ISVTX, are not supported. By
258
+ # default, the DACL is set to protected, meaning it does not inherit
259
+ # access control entries from parent objects. This can be changed by
260
+ # setting +protected+ to false. The owner of the object (with
261
+ # READ_CONTROL and WRITE_DACL access) can always change the
262
+ # mode. Only a user with the SE_BACKUP_NAME and SE_RESTORE_NAME
263
+ # privileges in their process token can change the mode for objects
264
+ # that they do not have read and write access to.
265
+ def set_mode(mode, path, protected = true)
266
+ owner_sid = get_owner(path)
267
+ group_sid = get_group(path)
268
+ well_known_world_sid = Win32::Security::SID::Everyone
269
+
270
+ owner_allow = STANDARD_RIGHTS_ALL | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES
271
+ group_allow = STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | SYNCHRONIZE
272
+ other_allow = STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | SYNCHRONIZE
273
+
274
+ MODE_TO_MASK.each do |k,v|
275
+ if ((mode >> 6) & k) == k
276
+ owner_allow |= v
277
+ end
278
+ if ((mode >> 3) & k) == k
279
+ group_allow |= v
280
+ end
281
+ if (mode & k) == k
282
+ other_allow |= v
283
+ end
284
+ end
285
+
286
+ # if owner and group the same, then map group permissions to the one owner ACE
287
+ isownergroup = owner_sid == group_sid
288
+ if isownergroup
289
+ owner_allow |= group_allow
290
+ end
291
+
292
+ set_acl(path, protected) do |acl|
293
+ #puts "ace: owner #{owner_sid}, mask 0x#{owner_allow.to_s(16)}"
294
+ add_access_allowed_ace(acl, owner_allow, owner_sid)
295
+
296
+ unless isownergroup
297
+ #puts "ace: group #{group_sid}, mask 0x#{group_allow.to_s(16)}"
298
+ add_access_allowed_ace(acl, group_allow, group_sid)
299
+ end
300
+
301
+ #puts "ace: other #{well_known_world_sid}, mask 0x#{other_allow.to_s(16)}"
302
+ add_access_allowed_ace(acl, other_allow, well_known_world_sid)
303
+
304
+ # add inheritable aces for child dirs and files that are created within the dir
305
+ if File.directory?(path)
306
+ inherit = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE
307
+
308
+ add_access_allowed_ace(acl, owner_allow, Win32::Security::SID::CreatorOwner, inherit)
309
+ add_access_allowed_ace(acl, group_allow, Win32::Security::SID::CreatorGroup, inherit)
310
+ add_access_allowed_ace(acl, other_allow, well_known_world_sid, inherit)
311
+ end
312
+ end
313
+
314
+ # if any ACE allows write, then clear readonly bit
315
+ if ((owner_allow | group_allow | other_allow ) & FILE_WRITE_DATA) == FILE_WRITE_DATA
316
+ remove_attributes(path, FILE_ATTRIBUTE_READONLY)
317
+ end
318
+
319
+ nil
320
+ end
321
+
322
+ # setting DACL requires both READ_CONTROL and WRITE_DACL access rights,
323
+ # and their respective privileges, SE_BACKUP_NAME and SE_RESTORE_NAME.
324
+ def set_acl(path, protected = true)
325
+ with_privilege(SE_BACKUP_NAME) do
326
+ with_privilege(SE_RESTORE_NAME) do
327
+ open_file(path, READ_CONTROL | WRITE_DAC) do |handle|
328
+ acl = 0.chr * 1024 # This can be increased later as needed
329
+
330
+ unless InitializeAcl(acl, acl.size, ACL_REVISION)
331
+ raise Puppet::Util::Windows::Error.new("Failed to initialize ACL")
332
+ end
333
+
334
+ raise Puppet::Util::Windows::Error.new("Invalid DACL") if IsValidAcl(acl) == 0
335
+
336
+ yield acl
337
+
338
+ # protected means the object does not inherit aces from its parent
339
+ info = DACL_SECURITY_INFORMATION
340
+ info |= protected ? PROTECTED_DACL_SECURITY_INFORMATION : UNPROTECTED_DACL_SECURITY_INFORMATION
341
+
342
+ # set the DACL
343
+ set_security_info(handle, info, acl)
344
+ end
345
+ end
346
+ end
347
+ end
348
+
349
+ def add_access_allowed_ace(acl, mask, sid, inherit = NO_INHERITANCE)
350
+ string_to_sid_ptr(sid) do |sid_ptr|
351
+ raise Puppet::Util::Windows::Error.new("Invalid SID") if IsValidSid(sid_ptr) == 0
352
+
353
+ if AddAccessAllowedAceEx(acl, ACL_REVISION, inherit, mask, sid_ptr) == 0
354
+ raise Puppet::Util::Windows::Error.new("Failed to add access control entry")
355
+ end
356
+ end
357
+ end
358
+
359
+ def add_access_denied_ace(acl, mask, sid)
360
+ string_to_sid_ptr(sid) do |sid_ptr|
361
+ raise Puppet::Util::Windows::Error.new("Invalid SID") if IsValidSid(sid_ptr) == 0
362
+
363
+ if AddAccessDeniedAce(acl, ACL_REVISION, mask, sid_ptr) == 0
364
+ raise Puppet::Util::Windows::Error.new("Failed to add access control entry")
365
+ end
366
+ end
367
+ end
368
+
369
+ def get_dacl(handle)
370
+ get_dacl_ptr(handle) do |dacl_ptr|
371
+ # REMIND: need to handle NULL DACL
372
+ raise Puppet::Util::Windows::Error.new("Invalid DACL") if IsValidAcl(dacl_ptr) == 0
373
+
374
+ # ACL structure, size and count are the important parts. The
375
+ # size includes both the ACL structure and all the ACEs.
376
+ #
377
+ # BYTE AclRevision
378
+ # BYTE Padding1
379
+ # WORD AclSize
380
+ # WORD AceCount
381
+ # WORD Padding2
382
+ acl_buf = 0.chr * 8
383
+ memcpy(acl_buf, dacl_ptr, acl_buf.size)
384
+ ace_count = acl_buf.unpack('CCSSS')[3]
385
+
386
+ dacl = []
387
+
388
+ # deny all
389
+ return dacl if ace_count == 0
390
+
391
+ 0.upto(ace_count - 1) do |i|
392
+ ace_ptr = [0].pack('L')
393
+ next if GetAce(dacl_ptr, i, ace_ptr) == 0
394
+
395
+ # ACE structures vary depending on the type. All structures
396
+ # begin with an ACE header, which specifies the type, flags
397
+ # and size of what follows. We are only concerned with
398
+ # ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACEs, which have the
399
+ # same structure:
400
+ #
401
+ # BYTE C AceType
402
+ # BYTE C AceFlags
403
+ # WORD S AceSize
404
+ # DWORD L ACCESS_MASK
405
+ # DWORD L Sid
406
+ # .. ...
407
+ # DWORD L Sid
408
+
409
+ ace_buf = 0.chr * 8
410
+ memcpy(ace_buf, ace_ptr.unpack('L')[0], ace_buf.size)
411
+
412
+ ace_type, ace_flags, size, mask = ace_buf.unpack('CCSL')
413
+
414
+ # skip aces that only serve to propagate inheritance
415
+ next if (ace_flags & INHERIT_ONLY_ACE).nonzero?
416
+
417
+ case ace_type
418
+ when ACCESS_ALLOWED_ACE_TYPE
419
+ sid_ptr = ace_ptr.unpack('L')[0] + 8 # address of ace_ptr->SidStart
420
+ raise Puppet::Util::Windows::Error.new("Failed to read DACL, invalid SID") unless IsValidSid(sid_ptr)
421
+ sid = sid_ptr_to_string(sid_ptr)
422
+ dacl << {:sid => sid, :type => ace_type, :mask => mask}
423
+ else
424
+ Puppet.warning "Unsupported access control entry type: 0x#{ace_type.to_s(16)}"
425
+ end
426
+ end
427
+
428
+ dacl
429
+ end
430
+ end
431
+
432
+ def get_dacl_ptr(handle)
433
+ dacl = [0].pack('L')
434
+ sd = [0].pack('L')
435
+
436
+ rv = GetSecurityInfo(
437
+ handle,
438
+ SE_FILE_OBJECT,
439
+ DACL_SECURITY_INFORMATION,
440
+ nil,
441
+ nil,
442
+ dacl, #dacl
443
+ nil, #sacl
444
+ sd) #sec desc
445
+ raise Puppet::Util::Windows::Error.new("Failed to get DACL") unless rv == ERROR_SUCCESS
446
+ begin
447
+ yield dacl.unpack('L')[0]
448
+ ensure
449
+ LocalFree(sd.unpack('L')[0])
450
+ end
451
+ end
452
+
453
+ # Set the security info on the specified handle.
454
+ def set_security_info(handle, info, ptr)
455
+ rv = SetSecurityInfo(
456
+ handle,
457
+ SE_FILE_OBJECT,
458
+ info,
459
+ (info & OWNER_SECURITY_INFORMATION) == OWNER_SECURITY_INFORMATION ? ptr : nil,
460
+ (info & GROUP_SECURITY_INFORMATION) == GROUP_SECURITY_INFORMATION ? ptr : nil,
461
+ (info & DACL_SECURITY_INFORMATION) == DACL_SECURITY_INFORMATION ? ptr : nil,
462
+ nil)
463
+ raise Puppet::Util::Windows::Error.new("Failed to set security information") unless rv == ERROR_SUCCESS
464
+ end
465
+
466
+ # Get the SID string, e.g. "S-1-5-32-544", for the specified handle
467
+ # and type of information (owner, group).
468
+ def get_security_info(handle, info)
469
+ sid = [0].pack('L')
470
+ sd = [0].pack('L')
471
+
472
+ rv = GetSecurityInfo(
473
+ handle,
474
+ SE_FILE_OBJECT,
475
+ info, # security info
476
+ info == OWNER_SECURITY_INFORMATION ? sid : nil,
477
+ info == GROUP_SECURITY_INFORMATION ? sid : nil,
478
+ nil, #dacl
479
+ nil, #sacl
480
+ sd) #sec desc
481
+ raise Puppet::Util::Windows::Error.new("Failed to get security information") unless rv == ERROR_SUCCESS
482
+
483
+ begin
484
+ return sid_ptr_to_string(sid.unpack('L')[0])
485
+ ensure
486
+ LocalFree(sd.unpack('L')[0])
487
+ end
488
+ end
489
+
490
+ # Convert a SID pointer to a string, e.g. "S-1-5-32-544".
491
+ def sid_ptr_to_string(psid)
492
+ sid_buf = 0.chr * 256
493
+ str_ptr = 0.chr * 4
494
+
495
+ raise Puppet::Util::Windows::Error.new("Invalid SID") if IsValidSid(psid) == 0
496
+
497
+ raise Puppet::Util::Windows::Error.new("Failed to convert binary SID") if ConvertSidToStringSid(psid, str_ptr) == 0
498
+
499
+ begin
500
+ strncpy(sid_buf, str_ptr.unpack('L')[0], sid_buf.size - 1)
501
+ sid_buf[sid_buf.size - 1] = 0.chr
502
+ return sid_buf.strip
503
+ ensure
504
+ LocalFree(str_ptr.unpack('L')[0])
505
+ end
506
+ end
507
+
508
+ # Convert a SID string, e.g. "S-1-5-32-544" to a pointer (containing the
509
+ # address of the binary SID structure). The returned value can be used in
510
+ # Win32 APIs that expect a PSID, e.g. IsValidSid.
511
+ def string_to_sid_ptr(string)
512
+ sid_buf = 0.chr * 80
513
+ string_addr = [string].pack('p*').unpack('L')[0]
514
+
515
+ raise Puppet::Util::Windows::Error.new("Failed to convert string SID: #{string}") unless ConvertStringSidToSid(string_addr, sid_buf)
516
+
517
+ sid_ptr = sid_buf.unpack('L')[0]
518
+ begin
519
+ if block_given?
520
+ yield sid_ptr
521
+ else
522
+ true
523
+ end
524
+ ensure
525
+ LocalFree(sid_ptr)
526
+ end
527
+ end
528
+
529
+ # Open an existing file with the specified access mode, and execute a
530
+ # block with the opened file HANDLE.
531
+ def open_file(path, access)
532
+ handle = CreateFile(
533
+ path,
534
+ access,
535
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
536
+ 0, # security_attributes
537
+ OPEN_EXISTING,
538
+ FILE_FLAG_BACKUP_SEMANTICS,
539
+ 0) # template
540
+ raise Puppet::Util::Windows::Error.new("Failed to open '#{path}'") if handle == INVALID_HANDLE_VALUE
541
+ begin
542
+ yield handle
543
+ ensure
544
+ CloseHandle(handle)
545
+ end
546
+ end
547
+
548
+ # Execute a block with the specified privilege enabled
549
+ def with_privilege(privilege)
550
+ set_privilege(privilege, true)
551
+ yield
552
+ ensure
553
+ set_privilege(privilege, false)
554
+ end
555
+
556
+ # Enable or disable a privilege. Note this doesn't add any privileges the
557
+ # user doesn't already has, it just enables privileges that are disabled.
558
+ def set_privilege(privilege, enable)
559
+ return unless Puppet.features.root?
560
+
561
+ with_process_token(TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY) do |token|
562
+ tmpLuid = 0.chr * 8
563
+
564
+ # Get the LUID for specified privilege.
565
+ if LookupPrivilegeValue("", privilege, tmpLuid) == 0
566
+ raise Puppet::Util::Windows::Error.new("Failed to lookup privilege")
567
+ end
568
+
569
+ # DWORD + [LUID + DWORD]
570
+ tkp = [1].pack('L') + tmpLuid + [enable ? SE_PRIVILEGE_ENABLED : 0].pack('L')
571
+
572
+ if AdjustTokenPrivileges(token, 0, tkp, tkp.length , nil, nil) == 0
573
+ raise Puppet::Util::Windows::Error.new("Failed to adjust process privileges")
574
+ end
575
+ end
576
+ end
577
+
578
+ # Execute a block with the current process token
579
+ def with_process_token(access)
580
+ token = 0.chr * 4
581
+
582
+ if OpenProcessToken(GetCurrentProcess(), access, token) == 0
583
+ raise Puppet::Util::Windows::Error.new("Failed to open process token")
584
+ end
585
+ begin
586
+ token = token.unpack('L')[0]
587
+
588
+ yield token
589
+ ensure
590
+ CloseHandle(token)
591
+ end
592
+ end
593
+ end