puppet 7.14.0-universal-darwin → 7.17.0-universal-darwin

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CODEOWNERS +1 -1
  3. data/Gemfile.lock +86 -25
  4. data/ext/systemd/puppet.service +1 -1
  5. data/lib/puppet/agent.rb +20 -2
  6. data/lib/puppet/application/agent.rb +3 -13
  7. data/lib/puppet/application/apply.rb +2 -2
  8. data/lib/puppet/application/lookup.rb +24 -28
  9. data/lib/puppet/configurer.rb +7 -3
  10. data/lib/puppet/defaults.rb +11 -2
  11. data/lib/puppet/functions/next.rb +18 -1
  12. data/lib/puppet/functions/tree_each.rb +0 -1
  13. data/lib/puppet/http/client.rb +23 -3
  14. data/lib/puppet/parameter.rb +19 -4
  15. data/lib/puppet/pops/evaluator/deferred_resolver.rb +46 -6
  16. data/lib/puppet/pops/functions/dispatcher.rb +10 -6
  17. data/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb +7 -6
  18. data/lib/puppet/pops/types/type_mismatch_describer.rb +22 -1
  19. data/lib/puppet/provider/package/puppetserver_gem.rb +7 -16
  20. data/lib/puppet/provider/package/yum.rb +8 -3
  21. data/lib/puppet/provider/user/directoryservice.rb +15 -8
  22. data/lib/puppet/ssl/ssl_provider.rb +75 -19
  23. data/lib/puppet/ssl/state_machine.rb +13 -17
  24. data/lib/puppet/transaction.rb +22 -0
  25. data/lib/puppet/type/exec.rb +1 -1
  26. data/lib/puppet/type/user.rb +3 -0
  27. data/lib/puppet/type.rb +20 -3
  28. data/lib/puppet/util/monkey_patches.rb +0 -2
  29. data/lib/puppet/util.rb +1 -0
  30. data/lib/puppet/version.rb +1 -1
  31. data/lib/puppet.rb +1 -14
  32. data/man/man5/puppet.conf.5 +11 -3
  33. data/man/man8/puppet-agent.8 +2 -2
  34. data/man/man8/puppet-apply.8 +1 -1
  35. data/man/man8/puppet-catalog.8 +1 -1
  36. data/man/man8/puppet-config.8 +1 -1
  37. data/man/man8/puppet-describe.8 +1 -1
  38. data/man/man8/puppet-device.8 +1 -1
  39. data/man/man8/puppet-doc.8 +1 -1
  40. data/man/man8/puppet-epp.8 +1 -1
  41. data/man/man8/puppet-facts.8 +1 -1
  42. data/man/man8/puppet-filebucket.8 +1 -1
  43. data/man/man8/puppet-generate.8 +1 -1
  44. data/man/man8/puppet-help.8 +1 -1
  45. data/man/man8/puppet-lookup.8 +1 -1
  46. data/man/man8/puppet-module.8 +1 -1
  47. data/man/man8/puppet-node.8 +1 -1
  48. data/man/man8/puppet-parser.8 +1 -1
  49. data/man/man8/puppet-plugin.8 +1 -1
  50. data/man/man8/puppet-report.8 +1 -1
  51. data/man/man8/puppet-resource.8 +1 -1
  52. data/man/man8/puppet-script.8 +1 -1
  53. data/man/man8/puppet-ssl.8 +1 -1
  54. data/man/man8/puppet.8 +2 -2
  55. data/spec/integration/application/agent_spec.rb +157 -0
  56. data/spec/integration/application/apply_spec.rb +74 -0
  57. data/spec/integration/application/lookup_spec.rb +64 -59
  58. data/spec/integration/application/resource_spec.rb +6 -2
  59. data/spec/integration/http/client_spec.rb +51 -4
  60. data/spec/lib/puppet_spec/https.rb +1 -1
  61. data/spec/lib/puppet_spec/puppetserver.rb +39 -2
  62. data/spec/unit/agent_spec.rb +6 -2
  63. data/spec/unit/application/agent_spec.rb +26 -16
  64. data/spec/unit/configurer_spec.rb +34 -3
  65. data/spec/unit/confiner_spec.rb +6 -6
  66. data/spec/unit/daemon_spec.rb +2 -11
  67. data/spec/unit/http/client_spec.rb +18 -0
  68. data/spec/unit/pops/evaluator/deferred_resolver_spec.rb +26 -0
  69. data/spec/unit/pops/loaders/loaders_spec.rb +1 -1
  70. data/spec/unit/pops/types/type_mismatch_describer_spec.rb +167 -1
  71. data/spec/unit/provider/package/puppetserver_gem_spec.rb +2 -2
  72. data/spec/unit/provider/user/directoryservice_spec.rb +1 -1
  73. data/spec/unit/ssl/ssl_provider_spec.rb +75 -1
  74. data/spec/unit/ssl/state_machine_spec.rb +1 -0
  75. data/spec/unit/util/windows_spec.rb +23 -0
  76. data/tasks/generate_cert_fixtures.rake +5 -4
  77. metadata +5 -3
@@ -3,6 +3,16 @@ require_relative '../../../puppet/parser/script_compiler'
3
3
  module Puppet::Pops
4
4
  module Evaluator
5
5
 
6
+ class DeferredValue
7
+ def initialize(proc)
8
+ @proc = proc
9
+ end
10
+
11
+ def resolve
12
+ @proc.call
13
+ end
14
+ end
15
+
6
16
  # Utility class to help resolve instances of Puppet::Pops::Types::PDeferredType::Deferred
7
17
  #
8
18
  class DeferredResolver
@@ -20,9 +30,9 @@ class DeferredResolver
20
30
  # are to be mixed into the scope
21
31
  # @return [nil] does not return anything - the catalog is modified as a side effect
22
32
  #
23
- def self.resolve_and_replace(facts, catalog, environment = catalog.environment_instance)
24
- compiler = Puppet::Parser::ScriptCompiler.new(environment, catalog.name, true)
25
- resolver = new(compiler)
33
+ def self.resolve_and_replace(facts, catalog, environment = catalog.environment_instance, preprocess_deferred = true)
34
+ compiler = Puppet::Parser::ScriptCompiler.new(environment, catalog.name, preprocess_deferred)
35
+ resolver = new(compiler, preprocess_deferred)
26
36
  resolver.set_facts_variable(facts)
27
37
  # TODO:
28
38
  # # When scripting the trusted data are always local, but set them anyway
@@ -53,11 +63,12 @@ class DeferredResolver
53
63
  resolver.resolve(value)
54
64
  end
55
65
 
56
- def initialize(compiler)
66
+ def initialize(compiler, preprocess_deferred = true)
57
67
  @compiler = compiler
58
68
  # Always resolve in top scope
59
69
  @scope = @compiler.topscope
60
70
  @deferred_class = Puppet::Pops::Types::TypeFactory.deferred.implementation_class
71
+ @preprocess_deferred = preprocess_deferred
61
72
  end
62
73
 
63
74
  # @param facts [Puppet::Node::Facts] the facts to set in $facts in the compiler's topscope
@@ -106,6 +117,24 @@ class DeferredResolver
106
117
  end
107
118
  end
108
119
 
120
+ def resolve_lazy_args(x)
121
+ if x.is_a?(DeferredValue)
122
+ x.resolve
123
+ elsif x.is_a?(Array)
124
+ x.map {|v| resolve_lazy_args(v) }
125
+ elsif x.is_a?(Hash)
126
+ result = {}
127
+ x.each_pair {|k,v| result[k] = resolve_lazy_args(v) }
128
+ result
129
+ elsif x.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive)
130
+ # rewrap in a new Sensitive after resolving any nested deferred values
131
+ Puppet::Pops::Types::PSensitiveType::Sensitive.new(resolve_lazy_args(x.unwrap))
132
+ else
133
+ x
134
+ end
135
+ end
136
+ private :resolve_lazy_args
137
+
109
138
  def resolve_future(f)
110
139
  # If any of the arguments to a future is a future it needs to be resolved first
111
140
  func_name = f.name
@@ -117,8 +146,19 @@ class DeferredResolver
117
146
  mapped_arguments.insert(0, @scope[var_name])
118
147
  end
119
148
 
120
- # call the function (name in deferred, or 'dig' for a variable)
121
- @scope.call_function(func_name, mapped_arguments)
149
+ if @preprocess_deferred
150
+ # call the function (name in deferred, or 'dig' for a variable)
151
+ @scope.call_function(func_name, mapped_arguments)
152
+ else
153
+ # call the function later
154
+ DeferredValue.new(
155
+ Proc.new {
156
+ # deferred functions can have nested deferred arguments
157
+ resolved_arguments = mapped_arguments.map { |arg| resolve_lazy_args(arg) }
158
+ @scope.call_function(func_name, resolved_arguments)
159
+ }
160
+ )
161
+ end
122
162
  end
123
163
 
124
164
  def map_arguments(args)
@@ -19,6 +19,10 @@ class Puppet::Pops::Functions::Dispatcher
19
19
  @dispatchers.empty?
20
20
  end
21
21
 
22
+ def find_matching_dispatcher(args, &block)
23
+ @dispatchers.find { |d| d.type.callable_with?(args, block) }
24
+ end
25
+
22
26
  # Dispatches the call to the first found signature (entry with matching type).
23
27
  #
24
28
  # @param instance [Puppet::Functions::Function] - the function to call
@@ -28,19 +32,19 @@ class Puppet::Pops::Functions::Dispatcher
28
32
  #
29
33
  # @api private
30
34
  def dispatch(instance, calling_scope, args, &block)
31
- found = @dispatchers.find { |d| d.type.callable_with?(args, block) }
32
- unless found
35
+
36
+ dispatcher = find_matching_dispatcher(args, &block)
37
+ unless dispatcher
33
38
  args_type = Puppet::Pops::Types::TypeCalculator.singleton.infer_set(block_given? ? args + [block] : args)
34
39
  raise ArgumentError, Puppet::Pops::Types::TypeMismatchDescriber.describe_signatures(instance.class.name, signatures, args_type)
35
40
  end
36
-
37
- if found.argument_mismatch_handler?
38
- msg = found.invoke(instance, calling_scope, args)
41
+ if dispatcher.argument_mismatch_handler?
42
+ msg = dispatcher.invoke(instance, calling_scope, args)
39
43
  raise ArgumentError, "'#{instance.class.name}' #{msg}"
40
44
  end
41
45
 
42
46
  catch(:next) do
43
- found.invoke(instance, calling_scope, args, &block)
47
+ dispatcher.invoke(instance, calling_scope, args, &block)
44
48
  end
45
49
  end
46
50
 
@@ -18,7 +18,7 @@ class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator
18
18
  def self.create(loader, typed_name, source_ref, ruby_code_string)
19
19
  # Assert content of 3x function by parsing
20
20
  assertion_result = []
21
- if assert_code(ruby_code_string, assertion_result)
21
+ if assert_code(ruby_code_string, source_ref, assertion_result)
22
22
  unless ruby_code_string.is_a?(String) && assertion_result.include?(:found_newfunction)
23
23
  raise ArgumentError, _("The code loaded from %{source_ref} does not seem to be a Puppet 3x API function - no 'newfunction' call.") % { source_ref: source_ref }
24
24
  end
@@ -69,15 +69,15 @@ class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator
69
69
  end
70
70
  private_class_method :get_binding
71
71
 
72
- def self.assert_code(code_string, result)
72
+ def self.assert_code(code_string, source_ref, result)
73
73
  ripped = Ripper.sexp(code_string)
74
74
  return false if ripped.nil? # Let the next real parse crash and tell where and what is wrong
75
- ripped.each {|x| walk(x, result) }
75
+ ripped.each {|x| walk(x, source_ref, result) }
76
76
  true
77
77
  end
78
78
  private_class_method :assert_code
79
79
 
80
- def self.walk(x, result)
80
+ def self.walk(x, source_ref, result)
81
81
  return unless x.is_a?(Array)
82
82
  first = x[0]
83
83
  case first
@@ -89,13 +89,14 @@ class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator
89
89
  when :def, :defs
90
90
  # There should not be any calls to def in a 3x function
91
91
  mname, mline = extract_name_line(find_identity(x))
92
- raise SecurityError, _("Illegal method definition of method '%{method_name}' on line %{line} in legacy function. See %{url} for more information") % {
92
+ raise SecurityError, _("Illegal method definition of method '%{method_name}' in source %{source_ref} on line %{line} in legacy function. See %{url} for more information") % {
93
93
  method_name: mname,
94
+ source_ref: source_ref,
94
95
  line: mline,
95
96
  url: "https://puppet.com/docs/puppet/latest/functions_refactor_legacy.html"
96
97
  }
97
98
  end
98
- x.each {|v| walk(v, result) }
99
+ x.each {|v| walk(v, source_ref, result) }
99
100
  end
100
101
  private_class_method :walk
101
102
 
@@ -581,6 +581,15 @@ module Types
581
581
  end
582
582
  end
583
583
 
584
+ def get_deferred_function_return_type(value)
585
+ func = Puppet.lookup(:loaders).private_environment_loader.
586
+ load(:function, value.name)
587
+ dispatcher = func.class.dispatcher.find_matching_dispatcher(value.arguments)
588
+ raise ArgumentError, "No matching arity found for #{func.class.name} with arguments #{value.arguments}" unless dispatcher
589
+ dispatcher.type.return_type
590
+ end
591
+ private :get_deferred_function_return_type
592
+
584
593
  # Validates that all entries in the _param_hash_ exists in the given param_struct, that their type conforms
585
594
  # with the corresponding param_struct element and that all required values are provided.
586
595
  # An error message is created for each problem found.
@@ -598,7 +607,19 @@ module Types
598
607
  value = param_hash[name]
599
608
  value_type = elem.value_type
600
609
  if param_hash.include?(name)
601
- result << describe(value_type, TypeCalculator.singleton.infer_set(value), [ParameterPathElement.new(name)]) unless value_type.instance?(value)
610
+ if Puppet::Pops::Types::TypeFactory.deferred.implementation_class == value.class
611
+ if (df_return_type = get_deferred_function_return_type(value))
612
+ result << describe(value_type, df_return_type, [ParameterPathElement.new(name)]) unless value_type.generalize.assignable?(df_return_type.generalize)
613
+ else
614
+ warning_text = _("Deferred function %{function_name} has no return_type, unable to guarantee value type during compilation.") %
615
+ {function_name: value.name }
616
+ Puppet.warn_once('deprecations',
617
+ "#{value.name}_deferred_warning",
618
+ warning_text)
619
+ end
620
+ else
621
+ result << describe(value_type, TypeCalculator.singleton.infer_set(value), [ParameterPathElement.new(name)]) unless value_type.instance?(value)
622
+ end
602
623
  else
603
624
  result << MissingParameter.new(nil, name) unless missing_ok || elem.key_type.is_a?(POptionalType)
604
625
  end
@@ -53,7 +53,7 @@ Puppet::Type.type(:package).provide :puppetserver_gem, :parent => :gem do
53
53
  end
54
54
 
55
55
  if options[:local]
56
- list = execute_rubygems_list_command(gem_regex)
56
+ list = execute_rubygems_list_command(command_options)
57
57
  else
58
58
  begin
59
59
  list = puppetservercmd(command_options)
@@ -137,7 +137,7 @@ Puppet::Type.type(:package).provide :puppetserver_gem, :parent => :gem do
137
137
  # for example: json (1.8.3 java)
138
138
  # but java platform gems should not be managed by this (or any) provider.
139
139
 
140
- def self.execute_rubygems_list_command(gem_regex)
140
+ def self.execute_rubygems_list_command(command_options)
141
141
  puppetserver_default_gem_home = '/opt/puppetlabs/server/data/puppetserver/jruby-gems'
142
142
  puppetserver_default_vendored_jruby_gems = '/opt/puppetlabs/server/data/puppetserver/vendored-jruby-gems'
143
143
  puppet_default_vendor_gems = '/opt/puppetlabs/puppet/lib/ruby/vendor_gems'
@@ -157,24 +157,15 @@ Puppet::Type.type(:package).provide :puppetserver_gem, :parent => :gem do
157
157
  gem_env['GEM_PATH'] = puppetserver_conf['jruby-puppet'].key?('gem-path') ? puppetserver_conf['jruby-puppet']['gem-path'].join(':') : puppetserver_default_gem_path
158
158
  end
159
159
  gem_env['GEM_SPEC_CACHE'] = "/tmp/#{$$}"
160
- Gem.paths = gem_env
161
-
162
- sio_inn = StringIO.new
163
- sio_out = StringIO.new
164
- sio_err = StringIO.new
165
- stream_ui = Gem::StreamUI.new(sio_inn, sio_out, sio_err, false)
166
- gem_list_cmd = Gem::Commands::ListCommand.new
167
- gem_list_cmd.options[:domain] = :local
168
- gem_list_cmd.options[:args] = [gem_regex] if gem_regex
169
- gem_list_cmd.ui = stream_ui
170
- gem_list_cmd.execute
160
+
161
+ # Remove the 'gem' from the command_options
162
+ command_options.shift
163
+ gem_out = execute_gem_command(Puppet::Type::Package::ProviderPuppet_gem.provider_command, command_options, gem_env)
171
164
 
172
165
  # There is no method exclude default gems from the local gem list,
173
166
  # for example: psych (default: 2.2.2)
174
167
  # but default gems should not be managed by this (or any) provider.
175
- gem_list = sio_out.string.lines.reject { |gem| gem =~ / \(default\: / }
168
+ gem_list = gem_out.lines.reject { |gem| gem =~ / \(default\: / }
176
169
  gem_list.join("\n")
177
- ensure
178
- Gem.clear_paths
179
170
  end
180
171
  end
@@ -204,7 +204,7 @@ defaultfor :osfamily => :redhat, :operatingsystemmajrelease => (4..7).to_a
204
204
  return should
205
205
  end
206
206
  versions = []
207
- available_versions(@resource[:name]).each do |version|
207
+ available_versions(@resource[:name], disablerepo, enablerepo, disableexcludes).each do |version|
208
208
  begin
209
209
  rpm_version = RPM_VERSION.parse(version)
210
210
  versions << rpm_version if should_range.include?(rpm_version)
@@ -225,8 +225,13 @@ defaultfor :osfamily => :redhat, :operatingsystemmajrelease => (4..7).to_a
225
225
  end
226
226
  end
227
227
 
228
- def available_versions(package_name)
229
- output = execute("yum list #{package_name} --showduplicates | sed -e '1,/Available Packages/ d' | awk '{print $2}'")
228
+ def available_versions(package_name, disablerepo, enablerepo, disableexcludes)
229
+ args = [command(:cmd), 'list', package_name, '--showduplicates']
230
+ args.concat(disablerepo.map { |repo| ["--disablerepo=#{repo}"] }.flatten)
231
+ args.concat(enablerepo.map { |repo| ["--enablerepo=#{repo}"] }.flatten)
232
+ args.concat(disableexcludes.map { |repo| ["--disableexcludes=#{repo}"] }.flatten)
233
+
234
+ output = execute("#{args.compact.join(' ')} | sed -e '1,/Available Packages/ d' | awk '{print $2}'")
230
235
  output.split("\n")
231
236
  end
232
237
 
@@ -147,9 +147,9 @@ Puppet::Type.type(:user).provide :directoryservice do
147
147
  else
148
148
  embedded_binary_plist = get_embedded_binary_plist(attribute_hash[:shadowhashdata])
149
149
  if embedded_binary_plist['SALTED-SHA512-PBKDF2']
150
- attribute_hash[:password] = get_salted_sha512_pbkdf2('entropy', embedded_binary_plist)
151
- attribute_hash[:salt] = get_salted_sha512_pbkdf2('salt', embedded_binary_plist)
152
- attribute_hash[:iterations] = get_salted_sha512_pbkdf2('iterations', embedded_binary_plist)
150
+ attribute_hash[:password] = get_salted_sha512_pbkdf2('entropy', embedded_binary_plist, attribute_hash[:name])
151
+ attribute_hash[:salt] = get_salted_sha512_pbkdf2('salt', embedded_binary_plist, attribute_hash[:name])
152
+ attribute_hash[:iterations] = get_salted_sha512_pbkdf2('iterations', embedded_binary_plist, attribute_hash[:name])
153
153
  elsif embedded_binary_plist['SALTED-SHA512']
154
154
  attribute_hash[:password] = get_salted_sha512(embedded_binary_plist)
155
155
  end
@@ -205,16 +205,18 @@ Puppet::Type.type(:user).provide :directoryservice do
205
205
  # according to which field is passed. Arguments passed are the hash
206
206
  # containing the value read from the 'ShadowHashData' key in the User's
207
207
  # plist, and the field to be read (one of 'entropy', 'salt', or 'iterations')
208
- def self.get_salted_sha512_pbkdf2(field, embedded_binary_plist)
208
+ def self.get_salted_sha512_pbkdf2(field, embedded_binary_plist, user_name = "")
209
209
  case field
210
210
  when 'salt', 'entropy'
211
- embedded_binary_plist['SALTED-SHA512-PBKDF2'][field].unpack('H*').first
211
+ value = embedded_binary_plist['SALTED-SHA512-PBKDF2'][field]
212
+ if value == nil
213
+ raise Puppet::Error, "Invalid #{field} given for user #{user_name}"
214
+ end
215
+ value.unpack('H*').first
212
216
  when 'iterations'
213
217
  Integer(embedded_binary_plist['SALTED-SHA512-PBKDF2'][field])
214
218
  else
215
- raise Puppet::Error, 'Puppet has tried to read an incorrect value from the ' +
216
- "SALTED-SHA512-PBKDF2 hash. Acceptable fields are 'salt', " +
217
- "'entropy', or 'iterations'."
219
+ raise Puppet::Error, "Puppet has tried to read an incorrect value from the user #{user_name} in the SALTED-SHA512-PBKDF2 hash. Acceptable fields are 'salt', 'entropy', or 'iterations'."
218
220
  end
219
221
  end
220
222
 
@@ -401,6 +403,11 @@ Puppet::Type.type(:user).provide :directoryservice do
401
403
  # we have to treat the ds cache just like you would in the password=
402
404
  # method.
403
405
  def salt=(value)
406
+ if (Puppet::Util::Package.versioncmp(self.class.get_os_version, '10.15') >= 0)
407
+ if value.length != 64
408
+ self.fail "macOS versions 10.15 and higher require the salt to be 32-bytes. Since Puppet's user resource requires the value to be hex encoded, the length of the salt's string must be 64. Please check your salt and try again."
409
+ end
410
+ end
404
411
  if (Puppet::Util::Package.versioncmp(self.class.get_os_version, '10.7') > 0)
405
412
  assert_full_pbkdf2_password
406
413
 
@@ -59,17 +59,19 @@ class Puppet::SSL::SSLProvider
59
59
  # refers to the cacerts bundle in the puppet-agent package.
60
60
  #
61
61
  # Connections made from the returned context will authenticate the server,
62
- # i.e. `VERIFY_PEER`, but will not use a client certificate and will not
63
- # perform revocation checking.
62
+ # i.e. `VERIFY_PEER`, but will not use a client certificate (unless requested)
63
+ # and will not perform revocation checking.
64
64
  #
65
65
  # @param cacerts [Array<OpenSSL::X509::Certificate>] Array of trusted CA certs
66
66
  # @param path [String, nil] A file containing additional trusted CA certs.
67
+ # @param include_client_cert [true, false] If true, the client cert will be added to the context
68
+ # allowing mutual TLS authentication. The default is false. If the client cert doesn't exist
69
+ # then the option will be ignored.
67
70
  # @return [Puppet::SSL::SSLContext] A context to use to create connections
68
71
  # @raise (see #create_context)
69
72
  # @api private
70
- def create_system_context(cacerts:, path: Puppet[:ssl_trust_store])
71
- store = create_x509_store(cacerts, [], false)
72
- store.set_default_paths
73
+ def create_system_context(cacerts:, path: Puppet[:ssl_trust_store], include_client_cert: false)
74
+ store = create_x509_store(cacerts, [], false, include_system_store: true)
73
75
 
74
76
  if path
75
77
  stat = Puppet::FileSystem.stat(path)
@@ -89,6 +91,29 @@ class Puppet::SSL::SSLProvider
89
91
  end
90
92
  end
91
93
 
94
+ if include_client_cert
95
+ cert_provider = Puppet::X509::CertProvider.new
96
+ private_key = cert_provider.load_private_key(Puppet[:certname], required: false)
97
+ unless private_key
98
+ Puppet.warning("Private key for '#{Puppet[:certname]}' does not exist")
99
+ end
100
+
101
+ client_cert = cert_provider.load_client_cert(Puppet[:certname], required: false)
102
+ unless client_cert
103
+ Puppet.warning("Client certificate for '#{Puppet[:certname]}' does not exist")
104
+ end
105
+
106
+ if private_key && client_cert
107
+ client_chain = resolve_client_chain(store, client_cert, private_key)
108
+
109
+ return Puppet::SSL::SSLContext.new(
110
+ store: store, cacerts: cacerts, crls: [],
111
+ private_key: private_key, client_cert: client_cert, client_chain: client_chain,
112
+ revocation: false
113
+ ).freeze
114
+ end
115
+ end
116
+
92
117
  Puppet::SSL::SSLContext.new(store: store, cacerts: cacerts, crls: [], revocation: false).freeze
93
118
  end
94
119
 
@@ -111,28 +136,21 @@ class Puppet::SSL::SSLProvider
111
136
  # @param client_cert [OpenSSL::X509::Certificate] client's cert whose public
112
137
  # key matches the `private_key`
113
138
  # @param revocation [:chain, :leaf, false] revocation mode
139
+ # @param include_system_store [true, false] Also trust system CA
114
140
  # @return [Puppet::SSL::SSLContext] A context to use to create connections
115
141
  # @raise [Puppet::SSL::CertVerifyError] There was an issue with
116
142
  # one of the certs or CRLs.
117
143
  # @raise [Puppet::SSL::SSLError] There was an issue with the
118
144
  # `private_key`.
119
145
  # @api private
120
- def create_context(cacerts:, crls:, private_key:, client_cert:, revocation: Puppet[:certificate_revocation])
146
+ def create_context(cacerts:, crls:, private_key:, client_cert:, revocation: Puppet[:certificate_revocation], include_system_store: false)
121
147
  raise ArgumentError, _("CA certs are missing") unless cacerts
122
148
  raise ArgumentError, _("CRLs are missing") unless crls
123
149
  raise ArgumentError, _("Private key is missing") unless private_key
124
150
  raise ArgumentError, _("Client cert is missing") unless client_cert
125
151
 
126
- store = create_x509_store(cacerts, crls, revocation)
127
- client_chain = verify_cert_with_store(store, client_cert)
128
-
129
- if !private_key.is_a?(OpenSSL::PKey::RSA) && !private_key.is_a?(OpenSSL::PKey::EC)
130
- raise Puppet::SSL::SSLError, _("Unsupported key '%{type}'") % { type: private_key.class.name }
131
- end
132
-
133
- unless client_cert.check_private_key(private_key)
134
- raise Puppet::SSL::SSLError, _("The certificate for '%{name}' does not match its private key") % { name: subject(client_cert) }
135
- end
152
+ store = create_x509_store(cacerts, crls, revocation, include_system_store: include_system_store)
153
+ client_chain = resolve_client_chain(store, client_cert, private_key)
136
154
 
137
155
  Puppet::SSL::SSLContext.new(
138
156
  store: store, cacerts: cacerts, crls: crls,
@@ -151,12 +169,13 @@ class Puppet::SSL::SSLProvider
151
169
  # @param password [String, nil] If the private key is encrypted, decrypt
152
170
  # it using the password. If the key is encrypted, but a password is
153
171
  # not specified, then the key cannot be loaded.
172
+ # @param include_system_store [true, false] Also trust system CA
154
173
  # @return [Puppet::SSL::SSLContext] A context to use to create connections
155
174
  # @raise [Puppet::SSL::CertVerifyError] There was an issue with
156
175
  # one of the certs or CRLs.
157
176
  # @raise [Puppet::Error] There was an issue with one of the required components.
158
177
  # @api private
159
- def load_context(certname: Puppet[:certname], revocation: Puppet[:certificate_revocation], password: nil)
178
+ def load_context(certname: Puppet[:certname], revocation: Puppet[:certificate_revocation], password: nil, include_system_store: false)
160
179
  cert = Puppet::X509::CertProvider.new
161
180
  cacerts = cert.load_cacerts(required: true)
162
181
  crls = case revocation
@@ -168,7 +187,7 @@ class Puppet::SSL::SSLProvider
168
187
  private_key = cert.load_private_key(certname, required: true, password: password)
169
188
  client_cert = cert.load_client_cert(certname, required: true)
170
189
 
171
- create_context(cacerts: cacerts, crls: crls, private_key: private_key, client_cert: client_cert, revocation: revocation)
190
+ create_context(cacerts: cacerts, crls: crls, private_key: private_key, client_cert: client_cert, revocation: revocation, include_system_store: include_system_store)
172
191
  rescue OpenSSL::PKey::PKeyError => e
173
192
  raise Puppet::SSL::SSLError.new(_("Failed to load private key for host '%{name}': %{message}") % { name: certname, message: e.message }, e)
174
193
  end
@@ -190,6 +209,27 @@ class Puppet::SSL::SSLProvider
190
209
  csr
191
210
  end
192
211
 
212
+ def print(ssl_context, alg = 'SHA256')
213
+ if Puppet::Util::Log.sendlevel?(:debug)
214
+ chain = ssl_context.client_chain
215
+ # print from root to client
216
+ chain.reverse.each_with_index do |cert, i|
217
+ digest = Puppet::SSL::Digest.new(alg, cert.to_der)
218
+ if i == chain.length - 1
219
+ Puppet.debug(_("Verified client certificate '%{subject}' fingerprint %{digest}") % {subject: cert.subject.to_utf8, digest: digest})
220
+ else
221
+ Puppet.debug(_("Verified CA certificate '%{subject}' fingerprint %{digest}") % {subject: cert.subject.to_utf8, digest: digest})
222
+ end
223
+ end
224
+ ssl_context.crls.each do |crl|
225
+ oid_values = Hash[crl.extensions.map { |ext| [ext.oid, ext.value] }]
226
+ crlNumber = oid_values['crlNumber'] || 'unknown'
227
+ authKeyId = (oid_values['authorityKeyIdentifier'] || 'unknown').chomp!
228
+ Puppet.debug("Using CRL '#{crl.issuer.to_utf8}' authorityKeyIdentifier '#{authKeyId}' crlNumber '#{crlNumber }'")
229
+ end
230
+ end
231
+ end
232
+
193
233
  private
194
234
 
195
235
  def default_flags
@@ -203,7 +243,7 @@ class Puppet::SSL::SSLProvider
203
243
  end
204
244
  end
205
245
 
206
- def create_x509_store(roots, crls, revocation)
246
+ def create_x509_store(roots, crls, revocation, include_system_store: false)
207
247
  store = OpenSSL::X509::Store.new
208
248
  store.purpose = OpenSSL::X509::PURPOSE_ANY
209
249
  store.flags = default_flags | revocation_mode(revocation)
@@ -211,6 +251,8 @@ class Puppet::SSL::SSLProvider
211
251
  roots.each { |cert| store.add_cert(cert) }
212
252
  crls.each { |crl| store.add_crl(crl) }
213
253
 
254
+ store.set_default_paths if include_system_store
255
+
214
256
  store
215
257
  end
216
258
 
@@ -234,6 +276,20 @@ class Puppet::SSL::SSLProvider
234
276
  end
235
277
  end
236
278
 
279
+ def resolve_client_chain(store, client_cert, private_key)
280
+ client_chain = verify_cert_with_store(store, client_cert)
281
+
282
+ if !private_key.is_a?(OpenSSL::PKey::RSA) && !private_key.is_a?(OpenSSL::PKey::EC)
283
+ raise Puppet::SSL::SSLError, _("Unsupported key '%{type}'") % { type: private_key.class.name }
284
+ end
285
+
286
+ unless client_cert.check_private_key(private_key)
287
+ raise Puppet::SSL::SSLError, _("The certificate for '%{name}' does not match its private key") % { name: subject(client_cert) }
288
+ end
289
+
290
+ client_chain
291
+ end
292
+
237
293
  def verify_cert_with_store(store, cert)
238
294
  # StoreContext#initialize accepts a chain argument, but it's set to [] because
239
295
  # puppet requires any intermediate CA certs needed to complete the client's
@@ -27,6 +27,15 @@ class Puppet::SSL::StateMachine
27
27
  detail.set_backtrace(cause.backtrace)
28
28
  Error.new(@machine, message, detail)
29
29
  end
30
+
31
+ def log_error(message)
32
+ # When running daemonized we set stdout to /dev/null, so write to the log instead
33
+ if Puppet[:daemonize]
34
+ Puppet.err(message)
35
+ else
36
+ $stdout.puts(message)
37
+ end
38
+ end
30
39
  end
31
40
 
32
41
  # Load existing CA certs or download them. Transition to NeedCRLs.
@@ -270,15 +279,15 @@ class Puppet::SSL::StateMachine
270
279
  def next_state
271
280
  time = @machine.waitforcert
272
281
  if time < 1
273
- puts _("Exiting now because the waitforcert setting is set to 0.")
282
+ log_error(_("Exiting now because the waitforcert setting is set to 0."))
274
283
  exit(1)
275
284
  elsif Time.now.to_i > @machine.wait_deadline
276
- puts _("Couldn't fetch certificate from CA server; you might still need to sign this agent's certificate (%{name}). Exiting now because the maxwaitforcert timeout has been exceeded.") % {name: Puppet[:certname] }
285
+ log_error(_("Couldn't fetch certificate from CA server; you might still need to sign this agent's certificate (%{name}). Exiting now because the maxwaitforcert timeout has been exceeded.") % {name: Puppet[:certname] })
277
286
  exit(1)
278
287
  else
279
288
  Puppet.info(_("Will try again in %{time} seconds.") % {time: time})
280
289
 
281
- # close persistent connections and session state before sleeping
290
+ # close http/tls and session state before sleeping
282
291
  Puppet.runtime[:http].close
283
292
  @machine.session = Puppet.runtime[:http].create_session
284
293
 
@@ -419,20 +428,7 @@ class Puppet::SSL::StateMachine
419
428
  def ensure_client_certificate
420
429
  final_state = run_machine(NeedLock.new(self), Done)
421
430
  ssl_context = final_state.ssl_context
422
-
423
- if Puppet::Util::Log.sendlevel?(:debug)
424
- chain = ssl_context.client_chain
425
- # print from root to client
426
- chain.reverse.each_with_index do |cert, i|
427
- digest = Puppet::SSL::Digest.new(@digest, cert.to_der)
428
- if i == chain.length - 1
429
- Puppet.debug(_("Verified client certificate '%{subject}' fingerprint %{digest}") % {subject: cert.subject.to_utf8, digest: digest})
430
- else
431
- Puppet.debug(_("Verified CA certificate '%{subject}' fingerprint %{digest}") % {subject: cert.subject.to_utf8, digest: digest})
432
- end
433
- end
434
- end
435
-
431
+ @ssl_provider.print(ssl_context, @digest)
436
432
  ssl_context
437
433
  end
438
434
 
@@ -276,6 +276,7 @@ class Puppet::Transaction
276
276
 
277
277
  # Evaluate a single resource.
278
278
  def eval_resource(resource, ancestor = nil)
279
+ resolve_resource(resource)
279
280
  propagate_failure(resource)
280
281
  if skip?(resource)
281
282
  resource_status(resource).skipped = true
@@ -464,6 +465,27 @@ class Puppet::Transaction
464
465
  public :skip?
465
466
  public :missing_tags?
466
467
 
468
+ def resolve_resource(resource)
469
+ return unless catalog.host_config?
470
+
471
+ deferred_validate = false
472
+
473
+ resource.eachparameter do |param|
474
+ if param.value.instance_of?(Puppet::Pops::Evaluator::DeferredValue)
475
+ # Puppet::Parameter#value= triggers validation and munging. Puppet::Property#value=
476
+ # overrides the method, but also triggers validation and munging, since we're
477
+ # setting the desired/should value.
478
+ resolved = param.value.resolve
479
+ # resource.notice("Resolved deferred value to #{resolved}")
480
+ param.value = resolved
481
+ deferred_validate = true
482
+ end
483
+ end
484
+
485
+ if deferred_validate
486
+ resource.validate_resource
487
+ end
488
+ end
467
489
  end
468
490
 
469
491
  require_relative 'transaction/report'
@@ -457,7 +457,7 @@ module Puppet
457
457
 
458
458
  exec { '/bin/echo root >> /usr/lib/cron/cron.allow':
459
459
  path => '/usr/bin:/usr/sbin:/bin',
460
- unless => 'grep root /usr/lib/cron/cron.allow 2>/dev/null',
460
+ unless => 'grep ^root$ /usr/lib/cron/cron.allow 2>/dev/null',
461
461
  }
462
462
 
463
463
  This would add `root` to the cron.allow file (on Solaris) unless
@@ -227,6 +227,9 @@ module Puppet
227
227
  * OS X 10.8 and higher use salted SHA512 PBKDF2 hashes. When managing passwords
228
228
  on these systems, the `salt` and `iterations` attributes need to be specified as
229
229
  well as the password.
230
+ * macOS 10.15 and higher require the salt to be 32-bytes. Since Puppet's user
231
+ resource requires the value to be hex encoded, the length of the salt's
232
+ string must be 64.
230
233
  * Windows passwords can be managed only in cleartext, because there is no Windows
231
234
  API for setting the password hash.
232
235
 
data/lib/puppet/type.rb CHANGED
@@ -2282,7 +2282,13 @@ class Type
2282
2282
  # @api public
2283
2283
  #
2284
2284
  def self.validate(&block)
2285
- define_method(:validate, &block)
2285
+ define_method(:unsafe_validate, &block)
2286
+
2287
+ define_method(:validate) do
2288
+ return if enum_for(:eachparameter).any? { |p| p.value.instance_of?(Puppet::Pops::Evaluator::DeferredValue) }
2289
+
2290
+ unsafe_validate
2291
+ end
2286
2292
  end
2287
2293
 
2288
2294
  # @return [String] The file from which this type originates from
@@ -2372,6 +2378,19 @@ class Type
2372
2378
 
2373
2379
  set_parameters(@original_parameters)
2374
2380
 
2381
+ validate_resource
2382
+
2383
+ set_sensitive_parameters(resource.sensitive_parameters)
2384
+ end
2385
+
2386
+ # Optionally validate the resource. This method is a noop if the type has not defined
2387
+ # a `validate` method using the puppet DSL. If validation fails, then an exception will
2388
+ # be raised with this resources as the context.
2389
+ #
2390
+ # @api public
2391
+ #
2392
+ # @return [void]
2393
+ def validate_resource
2375
2394
  begin
2376
2395
  self.validate if self.respond_to?(:validate)
2377
2396
  rescue Puppet::Error, ArgumentError => detail
@@ -2379,8 +2398,6 @@ class Type
2379
2398
  adderrorcontext(error, detail)
2380
2399
  raise error
2381
2400
  end
2382
-
2383
- set_sensitive_parameters(resource.sensitive_parameters)
2384
2401
  end
2385
2402
 
2386
2403
  protected