puppet 4.9.4 → 4.10.0

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 (124) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +6 -0
  3. data/ext/project_data.yaml +2 -2
  4. data/lib/hiera/puppet_function.rb +1 -1
  5. data/lib/puppet.rb +1 -0
  6. data/lib/puppet/application.rb +14 -0
  7. data/lib/puppet/application/inspect.rb +3 -0
  8. data/lib/puppet/defaults.rb +12 -2
  9. data/lib/puppet/etc.rb +125 -0
  10. data/lib/puppet/face/help.rb +1 -1
  11. data/lib/puppet/functions.rb +49 -4
  12. data/lib/puppet/functions/eyaml_lookup_key.rb +12 -3
  13. data/lib/puppet/functions/hocon_data.rb +9 -0
  14. data/lib/puppet/functions/json_data.rb +9 -0
  15. data/lib/puppet/functions/yaml_data.rb +9 -0
  16. data/lib/puppet/indirector/file_bucket_file/file.rb +69 -22
  17. data/lib/puppet/indirector/key/file.rb +2 -1
  18. data/lib/puppet/indirector/ssl_file.rb +24 -3
  19. data/lib/puppet/module.rb +28 -22
  20. data/lib/puppet/network/http/compression.rb +2 -1
  21. data/lib/puppet/parser/compiler.rb +15 -38
  22. data/lib/puppet/parser/functions/hiera.rb +1 -1
  23. data/lib/puppet/parser/functions/hiera_array.rb +1 -1
  24. data/lib/puppet/parser/functions/hiera_hash.rb +1 -1
  25. data/lib/puppet/parser/functions/hiera_include.rb +1 -1
  26. data/lib/puppet/parser/scope.rb +59 -17
  27. data/lib/puppet/pops/evaluator/callable_signature.rb +7 -0
  28. data/lib/puppet/pops/functions/dispatch.rb +18 -5
  29. data/lib/puppet/pops/functions/dispatcher.rb +7 -13
  30. data/lib/puppet/pops/issue_reporter.rb +1 -1
  31. data/lib/puppet/pops/issues.rb +84 -0
  32. data/lib/puppet/pops/loader/base_loader.rb +13 -5
  33. data/lib/puppet/pops/lookup/configured_data_provider.rb +8 -2
  34. data/lib/puppet/pops/lookup/data_dig_function_provider.rb +109 -19
  35. data/lib/puppet/pops/lookup/data_hash_function_provider.rb +19 -4
  36. data/lib/puppet/pops/lookup/data_provider.rb +43 -29
  37. data/lib/puppet/pops/lookup/environment_data_provider.rb +1 -1
  38. data/lib/puppet/pops/lookup/explainer.rb +1 -0
  39. data/lib/puppet/pops/lookup/function_provider.rb +36 -11
  40. data/lib/puppet/pops/lookup/global_data_provider.rb +18 -5
  41. data/lib/puppet/pops/lookup/hiera_config.rb +203 -84
  42. data/lib/puppet/pops/lookup/interpolation.rb +21 -6
  43. data/lib/puppet/pops/lookup/invocation.rb +14 -9
  44. data/lib/puppet/pops/lookup/location_resolver.rb +27 -0
  45. data/lib/puppet/pops/lookup/lookup_adapter.rb +59 -6
  46. data/lib/puppet/pops/lookup/lookup_key_function_provider.rb +9 -77
  47. data/lib/puppet/pops/lookup/module_data_provider.rb +27 -4
  48. data/lib/puppet/pops/parser/lexer2.rb +1 -1
  49. data/lib/puppet/pops/pcore.rb +3 -3
  50. data/lib/puppet/pops/types/p_object_type.rb +4 -6
  51. data/lib/puppet/pops/types/ruby_generator.rb +2 -2
  52. data/lib/puppet/pops/types/type_asserter.rb +3 -3
  53. data/lib/puppet/pops/types/type_mismatch_describer.rb +25 -7
  54. data/lib/puppet/pops/types/types.rb +20 -29
  55. data/lib/puppet/provider/exec.rb +4 -2
  56. data/lib/puppet/provider/nameservice.rb +8 -8
  57. data/lib/puppet/provider/selmodule/semodule.rb +20 -16
  58. data/lib/puppet/provider/service/src.rb +39 -39
  59. data/lib/puppet/provider/service/systemd.rb +1 -1
  60. data/lib/puppet/provider/user/aix.rb +7 -2
  61. data/lib/puppet/settings.rb +30 -17
  62. data/lib/puppet/ssl/base.rb +14 -1
  63. data/lib/puppet/ssl/certificate_authority.rb +4 -2
  64. data/lib/puppet/ssl/configuration.rb +4 -1
  65. data/lib/puppet/ssl/inventory.rb +10 -3
  66. data/lib/puppet/ssl/key.rb +7 -3
  67. data/lib/puppet/test/test_helper.rb +3 -0
  68. data/lib/puppet/type.rb +13 -1
  69. data/lib/puppet/type/exec.rb +16 -1
  70. data/lib/puppet/type/group.rb +17 -11
  71. data/lib/puppet/type/user.rb +3 -1
  72. data/lib/puppet/util.rb +1 -0
  73. data/lib/puppet/util/character_encoding.rb +95 -0
  74. data/lib/puppet/util/execution.rb +9 -6
  75. data/lib/puppet/util/reference.rb +4 -2
  76. data/lib/puppet/util/windows/file.rb +5 -1
  77. data/lib/puppet/version.rb +6 -2
  78. data/locales/config.yaml +1 -1
  79. data/locales/puppet.pot +18 -4
  80. data/spec/integration/ssl/autosign_spec.rb +18 -3
  81. data/spec/integration/ssl/key_spec.rb +104 -0
  82. data/spec/integration/type/user_spec.rb +13 -6
  83. data/spec/spec_helper.rb +7 -0
  84. data/spec/unit/application/inspect_spec.rb +9 -2
  85. data/spec/unit/data_providers/function_data_provider_spec.rb +2 -2
  86. data/spec/unit/etc_spec.rb +234 -0
  87. data/spec/unit/face/certificate_spec.rb +10 -2
  88. data/spec/unit/functions/dig_spec.rb +1 -1
  89. data/spec/unit/functions/hiera_spec.rb +40 -1
  90. data/spec/unit/functions/lookup_fixture_spec.rb +10 -10
  91. data/spec/unit/functions/lookup_spec.rb +1217 -357
  92. data/spec/unit/functions4_spec.rb +37 -1
  93. data/spec/unit/indirector/file_bucket_file/file_spec.rb +33 -2
  94. data/spec/unit/indirector/key/file_spec.rb +1 -1
  95. data/spec/unit/indirector/ssl_file_spec.rb +3 -3
  96. data/spec/unit/module_spec.rb +52 -59
  97. data/spec/unit/network/http/compression_spec.rb +39 -8
  98. data/spec/unit/parser/compiler_spec.rb +14 -0
  99. data/spec/unit/pops/loaders/loaders_spec.rb +21 -3
  100. data/spec/unit/pops/loaders/module_loaders_spec.rb +61 -0
  101. data/spec/unit/pops/lookup/context_spec.rb +56 -8
  102. data/spec/unit/pops/lookup/lookup_spec.rb +32 -1
  103. data/spec/unit/pops/parser/lexer2_spec.rb +8 -0
  104. data/spec/unit/pops/types/ruby_generator_spec.rb +48 -0
  105. data/spec/unit/pops/types/type_mismatch_describer_spec.rb +12 -3
  106. data/spec/unit/pops/types/types_spec.rb +6 -7
  107. data/spec/unit/provider/nameservice_spec.rb +12 -12
  108. data/spec/unit/provider/package/pkg_spec.rb +2 -0
  109. data/spec/unit/provider/service/src_spec.rb +5 -0
  110. data/spec/unit/ssl/base_spec.rb +9 -0
  111. data/spec/unit/ssl/certificate_authority_spec.rb +2 -2
  112. data/spec/unit/ssl/certificate_request_attributes_spec.rb +6 -0
  113. data/spec/unit/ssl/certificate_request_spec.rb +1 -1
  114. data/spec/unit/ssl/certificate_spec.rb +1 -1
  115. data/spec/unit/ssl/configuration_spec.rb +11 -2
  116. data/spec/unit/ssl/inventory_spec.rb +27 -3
  117. data/spec/unit/ssl/key_spec.rb +7 -7
  118. data/spec/unit/type/exec_spec.rb +41 -4
  119. data/spec/unit/type/file_spec.rb +4 -1
  120. data/spec/unit/util/character_encoding_spec.rb +88 -0
  121. data/spec/unit/util/execution_spec.rb +12 -0
  122. data/spec/unit/version_spec.rb +4 -0
  123. metadata +3803 -3808
  124. data/tasks/i18n.rake +0 -20
@@ -23,7 +23,7 @@ Puppet::Type.type(:service).provide :systemd, :parent => :base do
23
23
  defaultfor :osfamily => :redhat, :operatingsystem => :fedora
24
24
  defaultfor :osfamily => :suse
25
25
  defaultfor :osfamily => :coreos
26
- defaultfor :operatingsystem => :debian, :operatingsystemmajrelease => "8"
26
+ defaultfor :operatingsystem => :debian, :operatingsystemmajrelease => ["8", "stretch/sid", "9", "buster/sid"]
27
27
  defaultfor :operatingsystem => :ubuntu, :operatingsystemmajrelease => ["15.04","15.10","16.04","16.10"]
28
28
  defaultfor :operatingsystem => :cumuluslinux, :operatingsystemmajrelease => ["3"]
29
29
 
@@ -216,7 +216,9 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do
216
216
 
217
217
  def open_security_passwd
218
218
  # helper method for tests
219
- File.open("/etc/security/passwd", 'r')
219
+ # AIX reference indicates this file is ASCII
220
+ # https://www.ibm.com/support/knowledgecenter/en/ssw_aix_72/com.ibm.aix.files/passwd_security.htm
221
+ Puppet::FileSystem.open("/etc/security/passwd", nil, "r:ASCII")
220
222
  end
221
223
 
222
224
  #--------------------------------
@@ -255,7 +257,10 @@ Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do
255
257
  user = @resource[:name]
256
258
 
257
259
  # Puppet execute does not support strings as input, only files.
258
- tmpfile = Tempfile.new('puppet_#{user}_pw')
260
+ # The password is expected to be in an encrypted format given -e is specified:
261
+ # https://www.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.cmds1/chpasswd.htm
262
+ # /etc/security/passwd is specified as an ASCII file per the AIX documentation
263
+ tmpfile = Tempfile.new("puppet_#{user}_pw", :encoding => Encoding::ASCII)
259
264
  tmpfile << "#{user}:#{value}\n"
260
265
  tmpfile.close()
261
266
 
@@ -44,6 +44,8 @@ class Puppet::Settings
44
44
  # The acceptable sections of the puppet.conf configuration file.
45
45
  ALLOWED_SECTION_NAMES = ['main', 'master', 'agent', 'user'].freeze
46
46
 
47
+ NONE = 'none'.freeze
48
+
47
49
  # This method is intended for puppet internal use only; it is a convenience method that
48
50
  # returns reasonable application default settings values for a given run_mode.
49
51
  def self.app_defaults_for_run_mode(run_mode)
@@ -384,12 +386,12 @@ class Puppet::Settings
384
386
  end
385
387
  end
386
388
 
387
- def_delegator :@config, :each
389
+ def_delegators :@config, :each, :each_pair, :each_key
388
390
 
389
391
  # Iterate over each section name.
390
392
  def eachsection
391
393
  yielded = []
392
- @config.each do |name, object|
394
+ @config.each_value do |object|
393
395
  section = object.section
394
396
  unless yielded.include? section
395
397
  yield section
@@ -549,7 +551,7 @@ class Puppet::Settings
549
551
  if @config[:environment]
550
552
  env = self.value(:environment).to_sym
551
553
  else
552
- env = "none"
554
+ env = NONE
553
555
  end
554
556
 
555
557
  # Call any hooks we should be calling.
@@ -1046,26 +1048,37 @@ Generated on #{Time.now}.
1046
1048
  #
1047
1049
  # @raise [InterpolationError]
1048
1050
  def value(param, environment = nil, bypass_interpolation = false)
1049
- param = param.to_sym
1050
1051
  environment &&= environment.to_sym
1052
+ value_sym(param.to_sym, environment, bypass_interpolation)
1053
+ end
1051
1054
 
1052
- setting = @config[param]
1053
-
1054
- # Short circuit to nil for undefined settings.
1055
- return nil if setting.nil?
1056
-
1055
+ # Find the correct value using symbols and our search path.
1056
+ #
1057
+ # @param param [Symbol] The value to look up
1058
+ # @param environment [Symbol] The environment to check for the value
1059
+ # @param bypass_interpolation [true, false] Whether to skip interpolation
1060
+ #
1061
+ # @return [Object] The looked up value
1062
+ #
1063
+ # @raise [InterpolationError]
1064
+ def value_sym(param, environment = nil, bypass_interpolation = false)
1057
1065
  # Check the cache first. It needs to be a per-environment
1058
1066
  # cache so that we don't spread values from one env
1059
1067
  # to another.
1060
- if @cache[environment||"none"].has_key?(param)
1061
- return @cache[environment||"none"][param]
1062
- elsif bypass_interpolation
1063
- val = values(environment, self.preferred_run_mode).lookup(param)
1064
- else
1065
- val = values(environment, self.preferred_run_mode).interpolate(param)
1066
- end
1068
+ cached_env = @cache[environment || NONE]
1069
+
1070
+ # Avoid two lookups in cache_env unless val is nil. When it is, it's important
1071
+ # to check if the key is included so that further processing (that will result
1072
+ # in nil again) is avoided.
1073
+ val = cached_env[param]
1074
+ return val if !val.nil? || cached_env.include?(param)
1075
+
1076
+ # Short circuit to nil for undefined settings.
1077
+ return nil unless @config.include?(param)
1067
1078
 
1068
- @cache[environment||"none"][param] = val
1079
+ vals = values(environment, preferred_run_mode)
1080
+ val = bypass_interpolation ? vals.lookup(param) : vals.interpolate(param)
1081
+ cached_env[param] = val
1069
1082
  val
1070
1083
  end
1071
1084
 
@@ -81,7 +81,20 @@ class Puppet::SSL::Base
81
81
 
82
82
  # Read content from disk appropriately.
83
83
  def read(path)
84
- @content = wrapped_class.new(File.read(path))
84
+ # applies to Puppet::SSL::Certificate, Puppet::SSL::CertificateRequest, Puppet::SSL::CertificateRevocationList
85
+ # Puppet::SSL::Key uses this, but also provides its own override
86
+ # nothing derives from Puppet::SSL::Certificate, but it is called by a number of other SSL Indirectors:
87
+ # Puppet::SSL::Certificate::DisabledCa (:find, :save, :destroy)
88
+ # Puppet::Indirector::CertificateStatus::File (.indirection.find)
89
+ # Puppet::Network::HTTP::WEBrick (.indirection.find)
90
+ # Puppet::Network::HTTP::RackREST (.from_instance)
91
+ # Puppet::Network::HTTP::WEBrickREST (.from_instance)
92
+ # Puppet::SSL::CertificateAuthority (.new, .indirection.find, .indirection.save)
93
+ # Puppet::SSL::Host (.indirection.find)
94
+ # Puppet::SSL::Inventory (.indirection.search, implements its own add / rebuild / serials with encoding UTF8)
95
+ # Puppet::SSL::CertificateAuthority::Interface (.indirection.find)
96
+ # Puppet::SSL::Validator::DefaultValidator (.from_instance) / Puppet::SSL::Validator::NoValidator does nothing
97
+ @content = wrapped_class.new(Puppet::FileSystem.read(path, :encoding => Encoding::ASCII))
85
98
  end
86
99
 
87
100
  # Convert our thing to pem.
@@ -173,7 +173,8 @@ class Puppet::SSL::CertificateAuthority
173
173
  20.times { pass += (rand(74) + 48).chr }
174
174
 
175
175
  begin
176
- Puppet.settings.setting(:capass).open('w') { |f| f.print pass }
176
+ # random password is limited to ASCII characters 48 ('0') through 122 ('z')
177
+ Puppet.settings.setting(:capass).open('w:ASCII') { |f| f.print pass }
177
178
  rescue Errno::EACCES => detail
178
179
  raise Puppet::Error, "Could not write CA password: #{detail}", detail.backtrace
179
180
  end
@@ -216,7 +217,8 @@ class Puppet::SSL::CertificateAuthority
216
217
  # file so this one is considered used.
217
218
  def next_serial
218
219
  serial = 1
219
- Puppet.settings.setting(:serial).exclusive_open('a+') do |f|
220
+ # the serial is 4 hex digits - limited to ASCII
221
+ Puppet.settings.setting(:serial).exclusive_open('a+:ASCII') do |f|
220
222
  f.rewind
221
223
  serial = f.read.chomp.hex
222
224
  if serial == 0
@@ -50,7 +50,10 @@ class Configuration
50
50
 
51
51
  # read_file makes testing easier.
52
52
  def read_file(path)
53
- File.read(path)
53
+ # https://www.ietf.org/rfc/rfc2459.txt defines the x509 V3 certificate format
54
+ # CA bundles are concatenated X509 certificates, but may also include
55
+ # comments, which could have UTF-8 characters
56
+ Puppet::FileSystem.read(path, :encoding => Encoding::UTF_8)
54
57
  end
55
58
  private :read_file
56
59
  end
@@ -8,7 +8,10 @@ class Puppet::SSL::Inventory
8
8
  # Add a certificate to our inventory.
9
9
  def add(cert)
10
10
  cert = cert.content if cert.is_a?(Puppet::SSL::Certificate)
11
- Puppet.settings.setting(:cert_inventory).open("a") do |f|
11
+ # RFC 5280 says the cert subject may contain UTF8 - https://www.ietf.org/rfc/rfc5280.txt
12
+ # Note however that Puppet generated SSL files must only contain ASCII characters
13
+ # based on the validate_certname method of Puppet::SSL::Base
14
+ Puppet.settings.setting(:cert_inventory).open('a:UTF-8') do |f|
12
15
  f.print format(cert)
13
16
  end
14
17
  end
@@ -28,7 +31,8 @@ class Puppet::SSL::Inventory
28
31
  def rebuild
29
32
  Puppet.notice "Rebuilding inventory file"
30
33
 
31
- Puppet.settings.setting(:cert_inventory).open('w') do |f|
34
+ # RFC 5280 says the cert subject may contain UTF8 - https://www.ietf.org/rfc/rfc5280.txt
35
+ Puppet.settings.setting(:cert_inventory).open('w:UTF-8') do |f|
32
36
  Puppet::SSL::Certificate.indirection.search("*").each do |cert|
33
37
  f.print format(cert.content)
34
38
  end
@@ -40,7 +44,10 @@ class Puppet::SSL::Inventory
40
44
  def serials(name)
41
45
  return [] unless Puppet::FileSystem.exist?(@path)
42
46
 
43
- File.readlines(@path).collect do |line|
47
+ # RFC 5280 says the cert subject may contain UTF8 - https://www.ietf.org/rfc/rfc5280.txt
48
+ # Note however that Puppet generated SSL files must only contain ASCII characters
49
+ # based on the validate_certname method of Puppet::SSL::Base
50
+ File.readlines(@path, :encoding => Encoding::UTF_8).collect do |line|
44
51
  /^(\S+).+\/CN=#{name}$/.match(line)
45
52
  end.compact.map { |m| Integer(m[1]) }
46
53
  end
@@ -38,15 +38,19 @@ DOC
38
38
  def password
39
39
  return nil unless password_file and Puppet::FileSystem.exist?(password_file)
40
40
 
41
- ::File.read(password_file)
41
+ # Puppet generates files at the default Puppet[:capass] using ASCII
42
+ # User configured :passfile could be in any encoding
43
+ # Use BINARY given the string is passed to an OpenSSL API accepting bytes
44
+ # note this is only called internally
45
+ Puppet::FileSystem.read(password_file, :encoding => Encoding::BINARY)
42
46
  end
43
47
 
44
48
  # Optionally support specifying a password file.
45
49
  def read(path)
46
50
  return super unless password_file
47
51
 
48
- #@content = wrapped_class.new(::File.read(path), password)
49
- @content = wrapped_class.new(::File.read(path), password)
52
+ # RFC 1421 states PEM is 7-bit ASCII https://tools.ietf.org/html/rfc1421
53
+ @content = wrapped_class.new(Puppet::FileSystem.read(path, :encoding => Encoding::ASCII), password)
50
54
  end
51
55
 
52
56
  def to_s
@@ -192,6 +192,9 @@ module Puppet::Test
192
192
  $old_env.each {|k, v| Puppet::Util.set_env(k, v, mode) }
193
193
  end
194
194
 
195
+ # Clear all environments
196
+ Puppet.lookup(:environments).clear_all
197
+
195
198
  # Restore the load_path late, to avoid messing with stubs from the test.
196
199
  $LOAD_PATH.clear
197
200
  $old_load_path.each {|x| $LOAD_PATH << x }
data/lib/puppet/type.rb CHANGED
@@ -1218,6 +1218,10 @@ class Type
1218
1218
  resource = Puppet::Resource.new(self, title)
1219
1219
  resource.catalog = hash.delete(:catalog)
1220
1220
 
1221
+ if sensitive = hash.delete(:sensitive_parameters)
1222
+ resource.sensitive_parameters = sensitive
1223
+ end
1224
+
1221
1225
  hash.each do |param, value|
1222
1226
  resource[param] = value
1223
1227
  end
@@ -1291,7 +1295,9 @@ class Type
1291
1295
  end
1292
1296
 
1293
1297
  newmetaparam(:audit) do
1294
- desc "Marks a subset of this resource's unmanaged attributes for auditing. Accepts an
1298
+ desc "(This metaparameter is deprecated and will be ignored in a future release.)
1299
+
1300
+ Marks a subset of this resource's unmanaged attributes for auditing. Accepts an
1295
1301
  attribute name, an array of attribute names, or `all`.
1296
1302
 
1297
1303
  Auditing a resource attribute has two effects: First, whenever a catalog
@@ -1312,6 +1318,12 @@ class Type
1312
1318
  and the second run will log the edit made by Puppet.)"
1313
1319
 
1314
1320
  validate do |list|
1321
+ if Puppet.settings[:strict] != :off
1322
+ # Only warn if `audit` metaparam came from a manifest
1323
+ if file && line
1324
+ puppet_deprecation_warning(_("The `audit` metaparameter is deprecated and will be ignored in a future release."), { :line => line, :file => file })
1325
+ end
1326
+ end
1315
1327
  list = Array(list).collect {|p| p.to_sym}
1316
1328
  unless list == [:all]
1317
1329
  list.each do |param|
@@ -157,7 +157,12 @@ module Puppet
157
157
  end
158
158
 
159
159
  unless self.should.include?(@status.exitstatus.to_s)
160
- self.fail("#{self.resource[:command]} returned #{@status.exitstatus} instead of one of [#{self.should.join(",")}]")
160
+ if @resource.parameter(:command).sensitive
161
+ # Don't print sensitive commands in the clear
162
+ self.fail("[command redacted] returned #{@status.exitstatus} instead of one of [#{self.should.join(",")}]")
163
+ else
164
+ self.fail("'#{self.resource[:command]}' returned #{@status.exitstatus} instead of one of [#{self.should.join(",")}]")
165
+ end
161
166
  end
162
167
 
163
168
  event
@@ -597,5 +602,15 @@ module Puppet
597
602
  def current_username
598
603
  Etc.getpwuid(Process.uid).name
599
604
  end
605
+
606
+ private
607
+ def set_sensitive_parameters(sensitive_parameters)
608
+ # Respect sensitive commands
609
+ if sensitive_parameters.include?(:command)
610
+ sensitive_parameters.delete(:command)
611
+ parameter(:command).sensitive = true
612
+ end
613
+ super(sensitive_parameters)
614
+ end
600
615
  end
601
616
  end
@@ -79,10 +79,9 @@ module Puppet
79
79
  end
80
80
 
81
81
  newproperty(:members, :array_matching => :all, :required_features => :manages_members) do
82
- desc "The members of the group. For directory services where group
83
- membership is stored in the group objects, not the users. Use
84
- with auth_membership to determine whether the specified members
85
- are inclusive or the minimum."
82
+ desc "The members of the group. For platforms or directory services where group
83
+ membership is stored in the group objects, not the users. This parameter's
84
+ behavior can be configured with `auth_membership`."
86
85
 
87
86
  def change_to_s(currentvalue, newvalue)
88
87
  currentvalue = currentvalue.join(",") if currentvalue != :absent
@@ -118,9 +117,12 @@ module Puppet
118
117
  end
119
118
 
120
119
  newparam(:auth_membership, :boolean => true, :parent => Puppet::Parameter::Boolean) do
121
- desc "Whether the provider is authoritative for group membership. This
122
- must be set to true to allow setting the group to no members with
123
- `members => [],`."
120
+ desc "Configures the behavior of the `members` parameter.
121
+
122
+ * `false` (default) --- The provided list of group members is partial,
123
+ and Puppet **ignores** any members that aren't listed there.
124
+ * `true` --- The provided list of of group members is comprehensive, and
125
+ Puppet **purges** any members that aren't listed there."
124
126
  defaultto false
125
127
  end
126
128
 
@@ -146,7 +148,8 @@ module Puppet
146
148
  end
147
149
 
148
150
  newproperty(:attributes, :parent => Puppet::Property::KeyValue, :required_features => :manages_aix_lam) do
149
- desc "Specify group AIX attributes in an array of `key=value` pairs."
151
+ desc "Specify group AIX attributes, as an array of `'key=value'` strings. This
152
+ parameter's behavior can be configured with `attribute_membership`."
150
153
 
151
154
  def membership
152
155
  :attribute_membership
@@ -162,9 +165,12 @@ module Puppet
162
165
  end
163
166
 
164
167
  newparam(:attribute_membership) do
165
- desc "Whether specified attribute value pairs should be treated as the only attributes
166
- of the user or whether they should merely
167
- be treated as the minimum list."
168
+ desc "AIX only. Configures the behavior of the `attributes` parameter.
169
+
170
+ * `minimum` (default) --- The provided list of attributes is partial, and Puppet
171
+ **ignores** any attributes that aren't listed there.
172
+ * `inclusive` --- The provided list of attributes is comprehensive, and
173
+ Puppet **purges** any attributes that aren't listed there."
168
174
 
169
175
  newvalues(:inclusive, :minimum)
170
176
 
@@ -739,7 +739,9 @@ module Puppet
739
739
  def unknown_keys_in_file(keyfile)
740
740
  names = []
741
741
  name_index = 0
742
- File.new(keyfile).each do |line|
742
+ # RFC 4716 specifies UTF-8 allowed in public key files per https://www.ietf.org/rfc/rfc4716.txt
743
+ # the authorized_keys file may contain UTF-8 comments
744
+ Puppet::FileSystem.open(keyfile, nil, 'r:UTF-8').each do |line|
743
745
  next unless line =~ Puppet::Type.type(:ssh_authorized_key).keyline_regex
744
746
  # the name is stored in the 4th capture of the regex
745
747
  name = $4
data/lib/puppet/util.rb CHANGED
@@ -10,6 +10,7 @@ require 'puppet/util/platform'
10
10
  require 'puppet/util/symbolic_file_mode'
11
11
  require 'puppet/file_system/uniquefile'
12
12
  require 'securerandom'
13
+ require 'puppet/util/character_encoding'
13
14
 
14
15
  module Puppet
15
16
  module Util
@@ -0,0 +1,95 @@
1
+ # A module to centralize heuristics/practices for managing character encoding in Puppet
2
+
3
+ module Puppet::Util::CharacterEncoding
4
+ class << self
5
+ # Warning! This is a destructive method - the string supplied is modified!
6
+ # @api public
7
+ # @param [String] string a string to transcode / force_encode to utf-8
8
+ # @return [String] string if already utf-8, OR
9
+ # the same string with external encoding set to utf-8 if bytes are valid utf-8 OR
10
+ # the same string transcoded to utf-8 OR
11
+ # nil upon a failure to legitimately set external encoding or transcode string
12
+ def convert_to_utf_8!(string)
13
+ currently_valid = string.valid_encoding?
14
+
15
+ begin
16
+ if string.encoding == Encoding::UTF_8
17
+ if currently_valid
18
+ return string
19
+ else
20
+ # If a string is currently believed to be UTF-8, but is also not
21
+ # valid_encoding?, we have no recourse but to fail because we have no
22
+ # idea what encoding this string originally came from where it *was*
23
+ # valid - all we know is it's not currently valid UTF-8.
24
+ raise EncodingError
25
+ end
26
+ elsif valid_utf_8_bytes?(string)
27
+ # Before we try to transcode the string, check if it is valid UTF-8 as
28
+ # currently constitued (in its non-UTF-8 encoding), and if it is, limit
29
+ # ourselves to setting the external encoding of the string to UTF-8
30
+ # rather than actually transcoding it. We do this to handle
31
+ # a couple scenarios:
32
+
33
+ # The first scenario is that the string was originally valid UTF-8 but
34
+ # the current puppet run is not in a UTF-8 environment. In this case,
35
+ # the string will likely have invalid byte sequences (i.e.,
36
+ # string.valid_encoding? == false), and attempting to transcode will
37
+ # fail with Encoding::InvalidByteSequenceError, referencing the
38
+ # invalid byte sequence in the original, pre-transcode, string. We
39
+ # might have gotten here, for example, if puppet is run first in a
40
+ # user context with UTF-8 encoding (setting the "is" value to UTF-8)
41
+ # and then later run via cron without UTF-8 specified, resulting in in
42
+ # EN_US (ISO-8859-1) on many systems. In this scenario we're
43
+ # effectively best-guessing this string originated as UTF-8 and only
44
+ # set external encoding to UTF-8 - transcoding would have failed
45
+ # anyway.
46
+
47
+ # The second scenario (more rare, I expect) is that this string does
48
+ # NOT have invalid byte sequences (string.valid_encoding? == true),
49
+ # but is *ALSO valid unicode*.
50
+ # Our example case is "\u16A0" - "RUNIC LETTER FEHU FEOH FE"
51
+ # http://www.fileformat.info/info/unicode/char/16A0/index.htm
52
+ # 0xE1 0x9A 0xA0 / 225 154 160
53
+ # These bytes are valid in ISO-8859-1 but the character they represent
54
+ # transcodes cleanly in ruby to *different* characters in UTF-8.
55
+ # That's not what we want if the user intended the original string as
56
+ # UTF-8. We can only guess, so if the string is valid UTF-8 as
57
+ # currently constituted, we default to assuming the string originated
58
+ # in UTF-8 and do not transcode it - we only set external encoding.
59
+ return string.force_encoding(Encoding::UTF_8)
60
+ elsif currently_valid
61
+ # If the string is not currently valid UTF-8 but it can be transcoded
62
+ # (it is valid in its current encoding), we can guess this string was
63
+ # not originally unicode. Transcode it to UTF-8. For strings with
64
+ # original encodings like SHIFT_JIS, this should be the final result.
65
+ return string.encode!(Encoding::UTF_8)
66
+ else
67
+ # If the string is neither valid UTF-8 as-is nor valid in its current
68
+ # encoding, fail. It requires user remediation.
69
+ raise EncodingError
70
+ end
71
+ rescue EncodingError => detail
72
+ # Catch both our own self-determined failure to transcode as well as any
73
+ # error on ruby's part, ie Encoding::UndefinedConversionError on a
74
+ # failure to encode!.
75
+ Puppet.debug(_("%{error}: %{value} is not valid UTF-8 and cannot be transcoded by Puppet.") %
76
+ { error: detail.inspect, value: string.dump })
77
+ return nil
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ # Do our best to determine if a string is valid UTF-8 via String#valid_encoding? without permanently
84
+ # modifying or duplicating the string due to performance concerns
85
+ # @api private
86
+ # @param [String] string a string to test
87
+ # @return [Boolean] whether we think the string is UTF-8 or not
88
+ def valid_utf_8_bytes?(string)
89
+ original_encoding = string.encoding
90
+ valid = string.force_encoding(Encoding::UTF_8).valid_encoding?
91
+ string.force_encoding(original_encoding)
92
+ valid
93
+ end
94
+ end
95
+ end