puppet 6.25.1 → 6.28.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CODEOWNERS +1 -1
  3. data/Gemfile +2 -2
  4. data/Gemfile.lock +101 -34
  5. data/lib/puppet/agent.rb +47 -11
  6. data/lib/puppet/application/agent.rb +2 -12
  7. data/lib/puppet/application/lookup.rb +74 -24
  8. data/lib/puppet/concurrent/thread_local_singleton.rb +5 -3
  9. data/lib/puppet/configurer.rb +8 -14
  10. data/lib/puppet/defaults.rb +13 -3
  11. data/lib/puppet/face/generate.rb +2 -0
  12. data/lib/puppet/file_serving/metadata.rb +3 -0
  13. data/lib/puppet/file_system/file_impl.rb +7 -7
  14. data/lib/puppet/file_system/jruby.rb +1 -1
  15. data/lib/puppet/file_system/windows.rb +4 -4
  16. data/lib/puppet/file_system.rb +1 -1
  17. data/lib/puppet/functions/next.rb +18 -1
  18. data/lib/puppet/functions/tree_each.rb +0 -1
  19. data/lib/puppet/functions/versioncmp.rb +6 -2
  20. data/lib/puppet/generate/type.rb +9 -0
  21. data/lib/puppet/http/client.rb +22 -2
  22. data/lib/puppet/node.rb +1 -1
  23. data/lib/puppet/pops/parser/code_merger.rb +4 -4
  24. data/lib/puppet/pops/parser/egrammar.ra +2 -0
  25. data/lib/puppet/pops/parser/eparser.rb +813 -794
  26. data/lib/puppet/pops/serialization/to_data_converter.rb +6 -18
  27. data/lib/puppet/provider/package/puppetserver_gem.rb +7 -16
  28. data/lib/puppet/provider/package/windows/exe_package.rb +30 -1
  29. data/lib/puppet/provider/package/windows/package.rb +2 -1
  30. data/lib/puppet/provider/package/windows.rb +14 -1
  31. data/lib/puppet/provider/service/init.rb +5 -4
  32. data/lib/puppet/provider/user/directoryservice.rb +5 -0
  33. data/lib/puppet/ssl/ssl_provider.rb +75 -19
  34. data/lib/puppet/ssl/state_machine.rb +13 -17
  35. data/lib/puppet/ssl/verifier.rb +6 -0
  36. data/lib/puppet/transaction/persistence.rb +22 -12
  37. data/lib/puppet/type/exec.rb +1 -1
  38. data/lib/puppet/type/file/data_sync.rb +1 -1
  39. data/lib/puppet/type/user.rb +43 -38
  40. data/lib/puppet/util/json.rb +17 -0
  41. data/lib/puppet/util/log.rb +7 -2
  42. data/lib/puppet/util/monkey_patches.rb +6 -2
  43. data/lib/puppet/util/package.rb +25 -16
  44. data/lib/puppet/util/yaml.rb +21 -2
  45. data/lib/puppet/util.rb +1 -2
  46. data/lib/puppet/version.rb +1 -1
  47. data/lib/puppet.rb +2 -14
  48. data/locales/puppet.pot +5 -10454
  49. data/man/man5/puppet.conf.5 +21 -2
  50. data/man/man8/puppet-agent.8 +1 -1
  51. data/man/man8/puppet-apply.8 +1 -1
  52. data/man/man8/puppet-catalog.8 +1 -1
  53. data/man/man8/puppet-config.8 +1 -1
  54. data/man/man8/puppet-describe.8 +1 -1
  55. data/man/man8/puppet-device.8 +1 -1
  56. data/man/man8/puppet-doc.8 +1 -1
  57. data/man/man8/puppet-epp.8 +1 -1
  58. data/man/man8/puppet-facts.8 +1 -1
  59. data/man/man8/puppet-filebucket.8 +1 -1
  60. data/man/man8/puppet-generate.8 +1 -1
  61. data/man/man8/puppet-help.8 +1 -1
  62. data/man/man8/puppet-key.8 +1 -1
  63. data/man/man8/puppet-lookup.8 +9 -6
  64. data/man/man8/puppet-man.8 +1 -1
  65. data/man/man8/puppet-module.8 +1 -1
  66. data/man/man8/puppet-node.8 +1 -1
  67. data/man/man8/puppet-parser.8 +1 -1
  68. data/man/man8/puppet-plugin.8 +1 -1
  69. data/man/man8/puppet-report.8 +1 -1
  70. data/man/man8/puppet-resource.8 +1 -1
  71. data/man/man8/puppet-script.8 +1 -1
  72. data/man/man8/puppet-ssl.8 +1 -1
  73. data/man/man8/puppet-status.8 +1 -1
  74. data/man/man8/puppet.8 +2 -2
  75. data/spec/fixtures/unit/forge/bacula.json +1 -1
  76. data/spec/integration/application/agent_spec.rb +108 -0
  77. data/spec/integration/application/lookup_spec.rb +81 -50
  78. data/spec/integration/application/resource_spec.rb +6 -2
  79. data/spec/integration/http/client_spec.rb +51 -4
  80. data/spec/lib/puppet_spec/https.rb +1 -1
  81. data/spec/lib/puppet_spec/puppetserver.rb +39 -2
  82. data/spec/shared_contexts/l10n.rb +5 -0
  83. data/spec/unit/agent_spec.rb +28 -2
  84. data/spec/unit/application/agent_spec.rb +26 -16
  85. data/spec/unit/application/lookup_spec.rb +131 -10
  86. data/spec/unit/concurrent/thread_local_singleton_spec.rb +39 -0
  87. data/spec/unit/configurer_spec.rb +124 -61
  88. data/spec/unit/daemon_spec.rb +2 -11
  89. data/spec/unit/face/generate_spec.rb +64 -0
  90. data/spec/unit/file_system_spec.rb +34 -4
  91. data/spec/unit/forge/module_release_spec.rb +3 -3
  92. data/spec/unit/functions/versioncmp_spec.rb +40 -4
  93. data/spec/unit/http/client_spec.rb +18 -0
  94. data/spec/unit/node_spec.rb +6 -0
  95. data/spec/unit/pops/parser/parse_containers_spec.rb +2 -2
  96. data/spec/unit/pops/serialization/to_from_hr_spec.rb +0 -58
  97. data/spec/unit/pops/validator/validator_spec.rb +5 -0
  98. data/spec/unit/provider/package/puppetserver_gem_spec.rb +2 -2
  99. data/spec/unit/provider/package/windows/exe_package_spec.rb +17 -0
  100. data/spec/unit/provider/service/gentoo_spec.rb +6 -5
  101. data/spec/unit/provider/service/init_spec.rb +15 -9
  102. data/spec/unit/provider/service/openwrt_spec.rb +21 -29
  103. data/spec/unit/provider/service/redhat_spec.rb +3 -2
  104. data/spec/unit/ssl/ssl_provider_spec.rb +75 -1
  105. data/spec/unit/ssl/state_machine_spec.rb +1 -0
  106. data/spec/unit/transaction/persistence_spec.rb +51 -0
  107. data/spec/unit/type/user_spec.rb +0 -45
  108. data/spec/unit/util/json_spec.rb +126 -0
  109. data/spec/unit/util/windows_spec.rb +23 -0
  110. data/spec/unit/util/yaml_spec.rb +54 -29
  111. data/tasks/generate_cert_fixtures.rake +5 -4
  112. metadata +9 -3
@@ -14,8 +14,6 @@ module Serialization
14
14
  # @option options [Boolean] :local_reference use local references instead of duplicating complex entries
15
15
  # @option options [Boolean] :type_by_reference `true` if Object types are converted to references rather than embedded.
16
16
  # @option options [Boolean] :symbol_as_string `true` if Symbols should be converted to strings (with type loss)
17
- # @option options [Boolean] :force_symbol `false` if Symbols should not be converted (rich_data and symbol_as_string must be false)
18
- # @option options [Boolean] :silence_warnings `false` if warnings should be silenced
19
17
  # @option options [String] :message_prefix String to prepend to in warnings and errors
20
18
  # @return [Data] the processed result. An object assignable to `Data`.
21
19
  #
@@ -43,12 +41,6 @@ module Serialization
43
41
  @symbol_as_string = options[:symbol_as_string]
44
42
  @symbol_as_string = false if @symbol_as_string.nil?
45
43
 
46
- @force_symbol = options[:force_symbol]
47
- @force_symbol = false if @force_symbol.nil?
48
-
49
- @silence_warnings = options[:silence_warnings]
50
- @silence_warnings = false if @silence_warnings.nil?
51
-
52
44
  @rich_data = options[:rich_data]
53
45
  @rich_data = false if @rich_data.nil?
54
46
 
@@ -100,11 +92,7 @@ module Serialization
100
92
  elsif @rich_data
101
93
  { PCORE_TYPE_KEY => PCORE_TYPE_SYMBOL, PCORE_VALUE_KEY => value.to_s }
102
94
  else
103
- if @force_symbol
104
- value
105
- else
106
- @silence_warnings ? unknown_to_string(value) : unknown_to_string_with_warning(value)
107
- end
95
+ unknown_to_string_with_warning(value)
108
96
  end
109
97
  elsif value.instance_of?(Array)
110
98
  process(value) do
@@ -129,11 +117,7 @@ module Serialization
129
117
  { PCORE_TYPE_KEY => PCORE_TYPE_SENSITIVE, PCORE_VALUE_KEY => to_data(value.unwrap) }
130
118
  end
131
119
  else
132
- if @rich_data
133
- value_to_data_hash(value)
134
- else
135
- @silence_warnings ? unknown_to_string(value) : unknown_to_string_with_warning(value)
136
- end
120
+ unknown_to_data(value)
137
121
  end
138
122
  end
139
123
 
@@ -207,6 +191,10 @@ module Serialization
207
191
  v
208
192
  end
209
193
 
194
+ def unknown_to_data(value)
195
+ @rich_data ? value_to_data_hash(value) : unknown_to_string_with_warning(value)
196
+ end
197
+
210
198
  def unknown_key_to_string_with_warning(value)
211
199
  str = unknown_to_string(value)
212
200
  serialization_issue(Issues::SERIALIZATION_UNKNOWN_KEY_CONVERTED_TO_STRING, :path => path_to_s, :klass => value.class, :value => str)
@@ -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
@@ -17,6 +17,11 @@ class Puppet::Provider::Package::Windows
17
17
  'WindowsInstaller',
18
18
  ]
19
19
 
20
+ def self.register(path)
21
+ Puppet::Type::Package::ProviderWindows.paths ||= []
22
+ Puppet::Type::Package::ProviderWindows.paths << path
23
+ end
24
+
20
25
  # Return an instance of the package from the registry, or nil
21
26
  def self.from_registry(name, values)
22
27
  if valid?(name, values)
@@ -55,7 +60,31 @@ class Puppet::Provider::Package::Windows
55
60
  end
56
61
 
57
62
  def self.install_command(resource)
58
- munge(resource[:source])
63
+ file_location = resource[:source]
64
+ if file_location.start_with?('http://', 'https://')
65
+ tempfile = Tempfile.new(['','.exe'])
66
+ begin
67
+ uri = URI(Puppet::Util.uri_encode(file_location))
68
+ client = Puppet.runtime[:http]
69
+ client.get(uri, options: { include_system_store: true }) do |response|
70
+ raise Puppet::HTTP::ResponseError.new(response) unless response.success?
71
+
72
+ File.open(tempfile.path, 'wb') do |file|
73
+ response.read_body do |data|
74
+ file.write(data)
75
+ end
76
+ end
77
+ end
78
+ rescue => detail
79
+ raise Puppet::Error.new(_("Error when installing %{package}: %{detail}") % { package: resource[:name] ,detail: detail.message}, detail)
80
+ ensure
81
+ self.register(tempfile.path)
82
+ tempfile.close()
83
+ file_location = tempfile.path
84
+ end
85
+ end
86
+
87
+ munge(file_location)
59
88
  end
60
89
 
61
90
  def uninstall_command
@@ -67,7 +67,8 @@ class Puppet::Provider::Package::Windows
67
67
  # REMIND: what about msp, etc
68
68
  MsiPackage
69
69
  when /\.exe"?\Z/i
70
- fail(_("The source does not exist: '%{source}'") % { source: resource[:source] }) unless Puppet::FileSystem.exist?(resource[:source])
70
+ fail(_("The source does not exist: '%{source}'") % { source: resource[:source] }) unless
71
+ Puppet::FileSystem.exist?(resource[:source]) || resource[:source].start_with?('http://', 'https://')
71
72
  ExePackage
72
73
  else
73
74
  fail(_("Don't know how to install '%{source}'") % { source: resource[:source] })
@@ -30,6 +30,19 @@ Puppet::Type.type(:package).provide(:windows, :parent => Puppet::Provider::Packa
30
30
  has_feature :versionable
31
31
 
32
32
  attr_accessor :package
33
+ class << self
34
+ attr_accessor :paths
35
+ end
36
+
37
+ def self.post_resource_eval
38
+ @paths.each do |path|
39
+ begin
40
+ Puppet::FileSystem.unlink(path)
41
+ rescue => detail
42
+ raise Puppet::Error.new(_("Error when unlinking %{path}: %{detail}") % { path: path ,detail: detail.message}, detail)
43
+ end
44
+ end if @paths
45
+ end
33
46
 
34
47
  # Return an array of provider instances
35
48
  def self.instances
@@ -64,7 +77,7 @@ Puppet::Type.type(:package).provide(:windows, :parent => Puppet::Provider::Packa
64
77
 
65
78
  command = [installer.install_command(resource), install_options].flatten.compact.join(' ')
66
79
  working_dir = File.dirname(resource[:source])
67
- if !Puppet::FileSystem.exist?(working_dir) && resource[:source] =~ /\.msi"?\Z/i
80
+ unless Puppet::FileSystem.exist?(working_dir)
68
81
  working_dir = nil
69
82
  end
70
83
  output = execute(command, :failonfail => false, :combine => true, :cwd => working_dir, :suppress_window => true)
@@ -84,7 +84,7 @@ Puppet::Type.type(:service).provide :init, :parent => :base do
84
84
  defpath = [defpath] unless defpath.is_a? Array
85
85
  instances = []
86
86
  defpath.each do |path|
87
- unless FileTest.directory?(path)
87
+ unless Puppet::FileSystem.directory?(path)
88
88
  Puppet.debug "Service path #{path} does not exist"
89
89
  next
90
90
  end
@@ -97,8 +97,9 @@ Puppet::Type.type(:service).provide :init, :parent => :base do
97
97
  fullpath = File.join(path, name)
98
98
  next if name =~ /^\./
99
99
  next if exclude.include? name
100
- next if not FileTest.executable?(fullpath)
101
- next if not is_init?(fullpath)
100
+ next if Puppet::FileSystem.directory?(fullpath)
101
+ next unless Puppet::FileSystem.executable?(fullpath)
102
+ next unless is_init?(fullpath)
102
103
  instances << new(:name => name, :path => path, :hasstatus => true)
103
104
  end
104
105
  end
@@ -122,7 +123,7 @@ Puppet::Type.type(:service).provide :init, :parent => :base do
122
123
 
123
124
  def paths
124
125
  @paths ||= @resource[:path].find_all do |path|
125
- if File.directory?(path)
126
+ if Puppet::FileSystem.directory?(path)
126
127
  true
127
128
  else
128
129
  if Puppet::FileSystem.exist?(path)
@@ -401,6 +401,11 @@ Puppet::Type.type(:user).provide :directoryservice do
401
401
  # we have to treat the ds cache just like you would in the password=
402
402
  # method.
403
403
  def salt=(value)
404
+ if (Puppet::Util::Package.versioncmp(self.class.get_os_version, '10.15') >= 0)
405
+ if value.length != 64
406
+ 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."
407
+ end
408
+ end
404
409
  if (Puppet::Util::Package.versioncmp(self.class.get_os_version, '10.7') > 0)
405
410
  assert_full_pbkdf2_password
406
411
 
@@ -42,17 +42,19 @@ class Puppet::SSL::SSLProvider
42
42
  # refers to the cacerts bundle in the puppet-agent package.
43
43
  #
44
44
  # Connections made from the returned context will authenticate the server,
45
- # i.e. `VERIFY_PEER`, but will not use a client certificate and will not
46
- # perform revocation checking.
45
+ # i.e. `VERIFY_PEER`, but will not use a client certificate (unless requested)
46
+ # and will not perform revocation checking.
47
47
  #
48
48
  # @param cacerts [Array<OpenSSL::X509::Certificate>] Array of trusted CA certs
49
49
  # @param path [String, nil] A file containing additional trusted CA certs.
50
+ # @param include_client_cert [true, false] If true, the client cert will be added to the context
51
+ # allowing mutual TLS authentication. The default is false. If the client cert doesn't exist
52
+ # then the option will be ignored.
50
53
  # @return [Puppet::SSL::SSLContext] A context to use to create connections
51
54
  # @raise (see #create_context)
52
55
  # @api private
53
- def create_system_context(cacerts:, path: Puppet[:ssl_trust_store])
54
- store = create_x509_store(cacerts, [], false)
55
- store.set_default_paths
56
+ def create_system_context(cacerts:, path: Puppet[:ssl_trust_store], include_client_cert: false)
57
+ store = create_x509_store(cacerts, [], false, include_system_store: true)
56
58
 
57
59
  if path
58
60
  stat = Puppet::FileSystem.stat(path)
@@ -72,6 +74,29 @@ class Puppet::SSL::SSLProvider
72
74
  end
73
75
  end
74
76
 
77
+ if include_client_cert
78
+ cert_provider = Puppet::X509::CertProvider.new
79
+ private_key = cert_provider.load_private_key(Puppet[:certname], required: false)
80
+ unless private_key
81
+ Puppet.warning("Private key for '#{Puppet[:certname]}' does not exist")
82
+ end
83
+
84
+ client_cert = cert_provider.load_client_cert(Puppet[:certname], required: false)
85
+ unless client_cert
86
+ Puppet.warning("Client certificate for '#{Puppet[:certname]}' does not exist")
87
+ end
88
+
89
+ if private_key && client_cert
90
+ client_chain = resolve_client_chain(store, client_cert, private_key)
91
+
92
+ return Puppet::SSL::SSLContext.new(
93
+ store: store, cacerts: cacerts, crls: [],
94
+ private_key: private_key, client_cert: client_cert, client_chain: client_chain,
95
+ revocation: false
96
+ ).freeze
97
+ end
98
+ end
99
+
75
100
  Puppet::SSL::SSLContext.new(store: store, cacerts: cacerts, crls: [], revocation: false).freeze
76
101
  end
77
102
 
@@ -94,28 +119,21 @@ class Puppet::SSL::SSLProvider
94
119
  # @param client_cert [OpenSSL::X509::Certificate] client's cert whose public
95
120
  # key matches the `private_key`
96
121
  # @param revocation [:chain, :leaf, false] revocation mode
122
+ # @param include_system_store [true, false] Also trust system CA
97
123
  # @return [Puppet::SSL::SSLContext] A context to use to create connections
98
124
  # @raise [Puppet::SSL::CertVerifyError] There was an issue with
99
125
  # one of the certs or CRLs.
100
126
  # @raise [Puppet::SSL::SSLError] There was an issue with the
101
127
  # `private_key`.
102
128
  # @api private
103
- def create_context(cacerts:, crls:, private_key:, client_cert:, revocation: Puppet[:certificate_revocation])
129
+ def create_context(cacerts:, crls:, private_key:, client_cert:, revocation: Puppet[:certificate_revocation], include_system_store: false)
104
130
  raise ArgumentError, _("CA certs are missing") unless cacerts
105
131
  raise ArgumentError, _("CRLs are missing") unless crls
106
132
  raise ArgumentError, _("Private key is missing") unless private_key
107
133
  raise ArgumentError, _("Client cert is missing") unless client_cert
108
134
 
109
- store = create_x509_store(cacerts, crls, revocation)
110
- client_chain = verify_cert_with_store(store, client_cert)
111
-
112
- if !private_key.is_a?(OpenSSL::PKey::RSA) && !private_key.is_a?(OpenSSL::PKey::EC)
113
- raise Puppet::SSL::SSLError, _("Unsupported key '%{type}'") % { type: private_key.class.name }
114
- end
115
-
116
- unless client_cert.check_private_key(private_key)
117
- raise Puppet::SSL::SSLError, _("The certificate for '%{name}' does not match its private key") % { name: subject(client_cert) }
118
- end
135
+ store = create_x509_store(cacerts, crls, revocation, include_system_store: include_system_store)
136
+ client_chain = resolve_client_chain(store, client_cert, private_key)
119
137
 
120
138
  Puppet::SSL::SSLContext.new(
121
139
  store: store, cacerts: cacerts, crls: crls,
@@ -134,12 +152,13 @@ class Puppet::SSL::SSLProvider
134
152
  # @param password [String, nil] If the private key is encrypted, decrypt
135
153
  # it using the password. If the key is encrypted, but a password is
136
154
  # not specified, then the key cannot be loaded.
155
+ # @param include_system_store [true, false] Also trust system CA
137
156
  # @return [Puppet::SSL::SSLContext] A context to use to create connections
138
157
  # @raise [Puppet::SSL::CertVerifyError] There was an issue with
139
158
  # one of the certs or CRLs.
140
159
  # @raise [Puppet::Error] There was an issue with one of the required components.
141
160
  # @api private
142
- def load_context(certname: Puppet[:certname], revocation: Puppet[:certificate_revocation], password: nil)
161
+ def load_context(certname: Puppet[:certname], revocation: Puppet[:certificate_revocation], password: nil, include_system_store: false)
143
162
  cert = Puppet::X509::CertProvider.new
144
163
  cacerts = cert.load_cacerts(required: true)
145
164
  crls = case revocation
@@ -151,7 +170,7 @@ class Puppet::SSL::SSLProvider
151
170
  private_key = cert.load_private_key(certname, required: true, password: password)
152
171
  client_cert = cert.load_client_cert(certname, required: true)
153
172
 
154
- create_context(cacerts: cacerts, crls: crls, private_key: private_key, client_cert: client_cert, revocation: revocation)
173
+ create_context(cacerts: cacerts, crls: crls, private_key: private_key, client_cert: client_cert, revocation: revocation, include_system_store: include_system_store)
155
174
  rescue OpenSSL::PKey::PKeyError => e
156
175
  raise Puppet::SSL::SSLError.new(_("Failed to load private key for host '%{name}': %{message}") % { name: certname, message: e.message }, e)
157
176
  end
@@ -173,6 +192,27 @@ class Puppet::SSL::SSLProvider
173
192
  csr
174
193
  end
175
194
 
195
+ def print(ssl_context, alg = 'SHA256')
196
+ if Puppet::Util::Log.sendlevel?(:debug)
197
+ chain = ssl_context.client_chain
198
+ # print from root to client
199
+ chain.reverse.each_with_index do |cert, i|
200
+ digest = Puppet::SSL::Digest.new(alg, cert.to_der)
201
+ if i == chain.length - 1
202
+ Puppet.debug(_("Verified client certificate '%{subject}' fingerprint %{digest}") % {subject: cert.subject.to_utf8, digest: digest})
203
+ else
204
+ Puppet.debug(_("Verified CA certificate '%{subject}' fingerprint %{digest}") % {subject: cert.subject.to_utf8, digest: digest})
205
+ end
206
+ end
207
+ ssl_context.crls.each do |crl|
208
+ oid_values = Hash[crl.extensions.map { |ext| [ext.oid, ext.value] }]
209
+ crlNumber = oid_values['crlNumber'] || 'unknown'
210
+ authKeyId = (oid_values['authorityKeyIdentifier'] || 'unknown').chomp!
211
+ Puppet.debug("Using CRL '#{crl.issuer.to_utf8}' authorityKeyIdentifier '#{authKeyId}' crlNumber '#{crlNumber }'")
212
+ end
213
+ end
214
+ end
215
+
176
216
  private
177
217
 
178
218
  def default_flags
@@ -186,7 +226,7 @@ class Puppet::SSL::SSLProvider
186
226
  end
187
227
  end
188
228
 
189
- def create_x509_store(roots, crls, revocation)
229
+ def create_x509_store(roots, crls, revocation, include_system_store: false)
190
230
  store = OpenSSL::X509::Store.new
191
231
  store.purpose = OpenSSL::X509::PURPOSE_ANY
192
232
  store.flags = default_flags | revocation_mode(revocation)
@@ -194,6 +234,8 @@ class Puppet::SSL::SSLProvider
194
234
  roots.each { |cert| store.add_cert(cert) }
195
235
  crls.each { |crl| store.add_crl(crl) }
196
236
 
237
+ store.set_default_paths if include_system_store
238
+
197
239
  store
198
240
  end
199
241
 
@@ -217,6 +259,20 @@ class Puppet::SSL::SSLProvider
217
259
  end
218
260
  end
219
261
 
262
+ def resolve_client_chain(store, client_cert, private_key)
263
+ client_chain = verify_cert_with_store(store, client_cert)
264
+
265
+ if !private_key.is_a?(OpenSSL::PKey::RSA) && !private_key.is_a?(OpenSSL::PKey::EC)
266
+ raise Puppet::SSL::SSLError, _("Unsupported key '%{type}'") % { type: private_key.class.name }
267
+ end
268
+
269
+ unless client_cert.check_private_key(private_key)
270
+ raise Puppet::SSL::SSLError, _("The certificate for '%{name}' does not match its private key") % { name: subject(client_cert) }
271
+ end
272
+
273
+ client_chain
274
+ end
275
+
220
276
  def verify_cert_with_store(store, cert)
221
277
  # StoreContext#initialize accepts a chain argument, but it's set to [] because
222
278
  # 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
 
@@ -417,20 +426,7 @@ class Puppet::SSL::StateMachine
417
426
  def ensure_client_certificate
418
427
  final_state = run_machine(NeedLock.new(self), Done)
419
428
  ssl_context = final_state.ssl_context
420
-
421
- if Puppet::Util::Log.sendlevel?(:debug)
422
- chain = ssl_context.client_chain
423
- # print from root to client
424
- chain.reverse.each_with_index do |cert, i|
425
- digest = Puppet::SSL::Digest.new(@digest, cert.to_der)
426
- if i == chain.length - 1
427
- Puppet.debug(_("Verified client certificate '%{subject}' fingerprint %{digest}") % {subject: cert.subject.to_utf8, digest: digest})
428
- else
429
- Puppet.debug(_("Verified CA certificate '%{subject}' fingerprint %{digest}") % {subject: cert.subject.to_utf8, digest: digest})
430
- end
431
- end
432
- end
433
-
429
+ @ssl_provider.print(ssl_context, @digest)
434
430
  ssl_context
435
431
  end
436
432
 
@@ -115,6 +115,12 @@ class Puppet::SSL::Verifier
115
115
  return false
116
116
  end
117
117
 
118
+ # ruby-openssl#74ef8c0cc56b840b772240f2ee2b0fc0aafa2743 now sets the
119
+ # store_context error when the cert is mismatched
120
+ when OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH
121
+ @last_error = Puppet::SSL::CertMismatchError.new(peer_cert, @hostname)
122
+ return false
123
+
118
124
  when OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID
119
125
  crl = store_context.current_crl
120
126
  if crl && crl.last_update && crl.last_update < Time.now + FIVE_MINUTES_AS_SECONDS
@@ -6,6 +6,26 @@ require 'puppet/util/yaml'
6
6
  # as calculating corrective_change).
7
7
  # @api private
8
8
  class Puppet::Transaction::Persistence
9
+
10
+ def self.allowed_classes
11
+ @allowed_classes ||= [
12
+ Symbol,
13
+ Time,
14
+ Regexp,
15
+ # URI is excluded, because it serializes all instance variables including the
16
+ # URI parser. Better to serialize the URL encoded representation.
17
+ SemanticPuppet::Version,
18
+ # SemanticPuppet::VersionRange has many nested classes and is unlikely to be
19
+ # used directly, so ignore it
20
+ Puppet::Pops::Time::Timestamp,
21
+ Puppet::Pops::Time::TimeData,
22
+ Puppet::Pops::Time::Timespan,
23
+ Puppet::Pops::Types::PBinaryType::Binary,
24
+ # Puppet::Pops::Types::PSensitiveType::Sensitive values are excluded from
25
+ # the persistence store, ignore it.
26
+ ].freeze
27
+ end
28
+
9
29
  def initialize
10
30
  @old_data = {}
11
31
  @new_data = {"resources" => {}}
@@ -62,7 +82,7 @@ class Puppet::Transaction::Persistence
62
82
  result = nil
63
83
  Puppet::Util.benchmark(:debug, _("Loaded transaction store file in %{seconds} seconds")) do
64
84
  begin
65
- result = Puppet::Util::Yaml.safe_load_file(filename, [Symbol, Time])
85
+ result = Puppet::Util::Yaml.safe_load_file(filename, self.class.allowed_classes)
66
86
  rescue Puppet::Util::Yaml::YamlLoadError => detail
67
87
  Puppet.log_exception(detail, _("Transaction store file %{filename} is corrupt (%{detail}); replacing") % { filename: filename, detail: detail })
68
88
 
@@ -87,17 +107,7 @@ class Puppet::Transaction::Persistence
87
107
 
88
108
  # Save data from internal class to persistence store on disk.
89
109
  def save
90
- converted_data = Puppet::Pops::Serialization::ToDataConverter.convert(
91
- @new_data, {
92
- symbol_as_string: false,
93
- local_reference: false,
94
- type_by_reference: true,
95
- force_symbol: true,
96
- silence_warnings: true,
97
- message_prefix: to_s
98
- }
99
- )
100
- Puppet::Util::Yaml.dump(converted_data, Puppet[:transactionstorefile])
110
+ Puppet::Util::Yaml.dump(@new_data, Puppet[:transactionstorefile])
101
111
  end
102
112
 
103
113
  # Use the catalog and run_mode to determine if persistence should be enabled or not
@@ -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
@@ -79,7 +79,7 @@ module Puppet
79
79
  return :absent unless stat
80
80
  ftype = stat.ftype
81
81
  # Don't even try to manage the content on directories or links
82
- return nil if ["directory","link"].include?(ftype)
82
+ return nil if ['directory', 'link', 'fifo', 'socket'].include?(ftype)
83
83
 
84
84
  begin
85
85
  resource.parameter(:checksum).sum_file(resource[:path])