Skip to content

Commit 9ff9907

Browse files
author
Christopher Thorn
committed
Merge branch '6.x' into maint/main/4-14-2022_manual_merge_up
Conflicts with lib/puppet.rb Had to delete the http_pool and the ssl_host, while allowing the ssl_context change.
2 parents b5b9682 + 6a77bf0 commit 9ff9907

File tree

14 files changed

+283
-81
lines changed

14 files changed

+283
-81
lines changed

lib/puppet.rb

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -235,20 +235,7 @@ def self.base_context(settings)
235235

236236
{
237237
:environments => Puppet::Environments::Cached.new(Puppet::Environments::Combined.new(*loaders)),
238-
:ssl_context => proc {
239-
begin
240-
cert = Puppet::X509::CertProvider.new
241-
password = cert.load_private_key_password
242-
ssl = Puppet::SSL::SSLProvider.new
243-
ssl.load_context(certname: Puppet[:certname], password: password)
244-
rescue => e
245-
# TRANSLATORS: `message` is an already translated string of why SSL failed to initialize
246-
Puppet.log_exception(e, _("Failed to initialize SSL: %{message}") % { message: e.message })
247-
# TRANSLATORS: `puppet agent -t` is a command and should not be translated
248-
Puppet.err(_("Run `puppet agent -t`"))
249-
raise e
250-
end
251-
},
238+
:ssl_context => proc { Puppet.runtime[:http].default_ssl_context },
252239
:http_session => proc { Puppet.runtime[:http].create_session },
253240
:plugins => proc { Puppet::Plugins::Configuration.load_plugins },
254241
:rich_data => false

lib/puppet/agent.rb

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,29 @@ def run(client_options = {})
4545
result = nil
4646
wait_for_lock_deadline = nil
4747
block_run = Puppet::Application.controlled_run do
48-
splay client_options.fetch :splay, Puppet[:splay]
48+
# splay may sleep for awhile!
49+
splay(client_options.fetch(:splay, Puppet[:splay]))
50+
51+
# waiting for certs may sleep for awhile depending on onetime, waitforcert and maxwaitforcert!
52+
# this needs to happen before forking so that if we fail to obtain certs and try to exit, then
53+
# we exit the main process and not the forked child.
54+
ssl_context = wait_for_certificates(client_options)
55+
4956
result = run_in_fork(should_fork) do
5057
with_client(client_options[:transaction_uuid], client_options[:job_id]) do |client|
5158
client_args = client_options.merge(:pluginsync => Puppet::Configurer.should_pluginsync?)
5259
begin
60+
# lock may sleep for awhile depending on waitforlock and maxwaitforlock!
5361
lock do
5462
# NOTE: Timeout is pretty heinous as the location in which it
5563
# throws an error is entirely unpredictable, which means that
5664
# it can interrupt code blocks that perform cleanup or enforce
5765
# sanity. The only thing a Puppet agent should do after this
5866
# error is thrown is die with as much dignity as possible.
5967
Timeout.timeout(Puppet[:runtimeout], RunTimeoutError) do
60-
client.run(client_args)
68+
Puppet.override(ssl_context: ssl_context) do
69+
client.run(client_args)
70+
end
6171
end
6272
end
6373
rescue Puppet::LockError
@@ -84,6 +94,8 @@ def run(client_options = {})
8494
rescue StandardError => detail
8595
Puppet.log_exception(detail, _("Could not run %{client_class}: %{detail}") % { client_class: client_class, detail: detail })
8696
nil
97+
ensure
98+
Puppet.runtime[:http].close
8799
end
88100
end
89101
end
@@ -137,4 +149,10 @@ def with_client(transaction_uuid, job_id = nil)
137149
ensure
138150
@client = nil
139151
end
152+
153+
def wait_for_certificates(options)
154+
waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : Puppet[:waitforcert])
155+
sm = Puppet::SSL::StateMachine.new(waitforcert: waitforcert, onetime: Puppet[:onetime])
156+
sm.ensure_client_certificate
157+
end
140158
end

lib/puppet/application/agent.rb

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -383,15 +383,11 @@ def run_command
383383

384384
log_config if Puppet[:daemonize]
385385

386-
# run ssl state machine, waiting if needed
387-
ssl_context = wait_for_certificates
388-
389386
# Each application is responsible for pushing loaders onto the context.
390387
# Use the current environment that has already been established, though
391388
# it may change later during the configurer run.
392389
env = Puppet.lookup(:current_environment)
393-
Puppet.override(ssl_context: ssl_context,
394-
current_environment: env,
390+
Puppet.override(current_environment: env,
395391
loaders: Puppet::Pops::Loaders.new(env, true)) do
396392
if Puppet[:onetime]
397393
onetime(daemon)
@@ -434,7 +430,7 @@ def fingerprint
434430

435431
def onetime(daemon)
436432
begin
437-
exitstatus = daemon.agent.run({:job_id => options[:job_id], :start_time => options[:start_time]})
433+
exitstatus = daemon.agent.run({:job_id => options[:job_id], :start_time => options[:start_time], :waitforcert => options[:waitforcert]})
438434
rescue => detail
439435
Puppet.log_exception(detail)
440436
end
@@ -524,10 +520,4 @@ def daemonize_process_when(should_daemonize)
524520

525521
daemon
526522
end
527-
528-
def wait_for_certificates
529-
waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : Puppet[:waitforcert])
530-
sm = Puppet::SSL::StateMachine.new(waitforcert: waitforcert)
531-
sm.ensure_client_certificate
532-
end
533523
end

lib/puppet/http/client.rb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class Puppet::HTTP::Client
9898
# used if :include_system_store is set to true
9999
# @param [Integer] redirect_limit default number of HTTP redirections to allow
100100
# in a given request. Can also be specified per-request.
101-
# @param [Integer] retry_limit number of HTTP reties allowed in a given
101+
# @param [Integer] retry_limit number of HTTP retries allowed in a given
102102
# request
103103
#
104104
def initialize(pool: Puppet::HTTP::Pool.new(Puppet[:http_keepalive_timeout]), ssl_context: nil, system_ssl_context: nil, redirect_limit: 10, retry_limit: 100)
@@ -300,6 +300,24 @@ def delete(url, headers: {}, params: {}, options: {})
300300
# @api public
301301
def close
302302
@pool.close
303+
@default_ssl_context = nil
304+
@default_system_ssl_context = nil
305+
end
306+
307+
def default_ssl_context
308+
cert = Puppet::X509::CertProvider.new
309+
password = cert.load_private_key_password
310+
311+
ssl = Puppet::SSL::SSLProvider.new
312+
ctx = ssl.load_context(certname: Puppet[:certname], password: password)
313+
ssl.print(ctx)
314+
ctx
315+
rescue => e
316+
# TRANSLATORS: `message` is an already translated string of why SSL failed to initialize
317+
Puppet.log_exception(e, _("Failed to initialize SSL: %{message}") % { message: e.message })
318+
# TRANSLATORS: `puppet agent -t` is a command and should not be translated
319+
Puppet.err(_("Run `puppet agent -t`"))
320+
raise e
303321
end
304322

305323
protected
@@ -459,6 +477,8 @@ def system_ssl_context
459477

460478
ssl = Puppet::SSL::SSLProvider.new
461479
@default_system_ssl_context = ssl.create_system_context(cacerts: cacerts)
480+
ssl.print(@default_system_ssl_context)
481+
@default_system_ssl_context
462482
end
463483

464484
def apply_auth(request, basic_auth)

lib/puppet/ssl/ssl_provider.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,27 @@ def verify_request(csr, public_key)
191191
csr
192192
end
193193

194+
def print(ssl_context, alg = 'SHA256')
195+
if Puppet::Util::Log.sendlevel?(:debug)
196+
chain = ssl_context.client_chain
197+
# print from root to client
198+
chain.reverse.each_with_index do |cert, i|
199+
digest = Puppet::SSL::Digest.new(alg, cert.to_der)
200+
if i == chain.length - 1
201+
Puppet.debug(_("Verified client certificate '%{subject}' fingerprint %{digest}") % {subject: cert.subject.to_utf8, digest: digest})
202+
else
203+
Puppet.debug(_("Verified CA certificate '%{subject}' fingerprint %{digest}") % {subject: cert.subject.to_utf8, digest: digest})
204+
end
205+
end
206+
ssl_context.crls.each do |crl|
207+
oid_values = Hash[crl.extensions.map { |ext| [ext.oid, ext.value] }]
208+
crlNumber = oid_values['crlNumber'] || 'unknown'
209+
authKeyId = (oid_values['authorityKeyIdentifier'] || 'unknown').chomp!
210+
Puppet.debug("Using CRL '#{crl.issuer.to_utf8}' authorityKeyIdentifier '#{authKeyId}' crlNumber '#{crlNumber }'")
211+
end
212+
end
213+
end
214+
194215
private
195216

196217
def default_flags

lib/puppet/ssl/state_machine.rb

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ def to_error(message, cause)
2727
detail.set_backtrace(cause.backtrace)
2828
Error.new(@machine, message, detail)
2929
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
3039
end
3140

3241
# Load existing CA certs or download them. Transition to NeedCRLs.
@@ -270,15 +279,15 @@ def initialize(machine)
270279
def next_state
271280
time = @machine.waitforcert
272281
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."))
274283
exit(1)
275284
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] })
277286
exit(1)
278287
else
279288
Puppet.info(_("Will try again in %{time} seconds.") % {time: time})
280289

281-
# close persistent connections and session state before sleeping
290+
# close http/tls and session state before sleeping
282291
Puppet.runtime[:http].close
283292
@machine.session = Puppet.runtime[:http].create_session
284293

@@ -419,20 +428,7 @@ def ensure_ca_certificates
419428
def ensure_client_certificate
420429
final_state = run_machine(NeedLock.new(self), Done)
421430
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)
436432
ssl_context
437433
end
438434

spec/integration/application/agent_spec.rb

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'puppet_spec/puppetserver'
44
require 'puppet_spec/compiler'
55
require 'puppet_spec/https'
6+
require 'puppet/application/agent'
67

78
describe "puppet agent", unless: Puppet::Util::Platform.jruby? do
89
include PuppetSpec::Files
@@ -740,4 +741,111 @@ def with_another_agent_running(&block)
740741
end
741742
end
742743
end
744+
745+
context "ssl" do
746+
context "bootstrapping" do
747+
before :each do
748+
# reconfigure ssl to non-existent dir and files to force bootstrapping
749+
dir = tmpdir('ssl')
750+
Puppet[:ssldir] = dir
751+
Puppet[:localcacert] = File.join(dir, 'ca.pem')
752+
Puppet[:hostcrl] = File.join(dir, 'crl.pem')
753+
Puppet[:hostprivkey] = File.join(dir, 'cert.pem')
754+
Puppet[:hostcert] = File.join(dir, 'key.pem')
755+
756+
Puppet[:daemonize] = false
757+
Puppet[:logdest] = 'console'
758+
Puppet[:log_level] = 'info'
759+
end
760+
761+
it "exits if the agent is not allowed to wait" do
762+
Puppet[:waitforcert] = 0
763+
764+
server.start_server do |port|
765+
Puppet[:serverport] = port
766+
expect {
767+
agent.run
768+
}.to exit_with(1)
769+
.and output(%r{Exiting now because the waitforcert setting is set to 0}).to_stdout
770+
.and output(%r{Failed to submit the CSR, HTTP response was 404}).to_stderr
771+
end
772+
end
773+
774+
it "exits if the maxwaitforcert time is exceeded" do
775+
Puppet[:waitforcert] = 1
776+
Puppet[:maxwaitforcert] = 1
777+
778+
server.start_server do |port|
779+
Puppet[:serverport] = port
780+
expect {
781+
agent.run
782+
}.to exit_with(1)
783+
.and output(%r{Couldn't fetch certificate from CA server; you might still need to sign this agent's certificate \(127.0.0.1\). Exiting now because the maxwaitforcert timeout has been exceeded.}).to_stdout
784+
.and output(%r{Failed to submit the CSR, HTTP response was 404}).to_stderr
785+
end
786+
end
787+
end
788+
789+
def copy_fixtures(sources, dest)
790+
ssldir = File.join(PuppetSpec::FIXTURE_DIR, 'ssl')
791+
File.open(dest, 'w') do |f|
792+
sources.each do |s|
793+
f.write(File.read(File.join(ssldir, s)))
794+
end
795+
end
796+
end
797+
798+
it "reloads the CRL between runs" do
799+
Puppet[:localcacert] = ca = tmpfile('ca')
800+
Puppet[:hostcrl] = crl = tmpfile('crl')
801+
Puppet[:hostcert] = cert = tmpfile('cert')
802+
Puppet[:hostprivkey] = key = tmpfile('key')
803+
804+
copy_fixtures(%w[ca.pem intermediate.pem], ca)
805+
copy_fixtures(%w[crl.pem intermediate-crl.pem], crl)
806+
copy_fixtures(%w[127.0.0.1.pem], cert)
807+
copy_fixtures(%w[127.0.0.1-key.pem], key)
808+
809+
revoked = cert_fixture('revoked.pem')
810+
revoked_key = key_fixture('revoked-key.pem')
811+
812+
mounts = {}
813+
mounts[:catalog] = -> (req, res) {
814+
catalog = compile_to_catalog(<<~MANIFEST, node)
815+
file { '#{cert}':
816+
ensure => file,
817+
content => '#{revoked}'
818+
}
819+
file { '#{key}':
820+
ensure => file,
821+
content => '#{revoked_key}'
822+
}
823+
MANIFEST
824+
825+
res.body = formatter.render(catalog)
826+
res['Content-Type'] = formatter.mime
827+
}
828+
829+
server.start_server(mounts: mounts) do |port|
830+
Puppet[:serverport] = port
831+
Puppet[:daemonize] = false
832+
Puppet[:runinterval] = 1
833+
Puppet[:waitforcert] = 1
834+
Puppet[:maxwaitforcert] = 1
835+
836+
# simulate two runs of the agent, then return so we don't infinite loop
837+
allow_any_instance_of(Puppet::Daemon).to receive(:run_event_loop) do |instance|
838+
instance.agent.run(splay: false)
839+
instance.agent.run(splay: false)
840+
end
841+
842+
agent.command_line.args << '--verbose'
843+
expect {
844+
agent.run
845+
}.to exit_with(1)
846+
.and output(%r{Exiting now because the maxwaitforcert timeout has been exceeded}).to_stdout
847+
.and output(%r{Certificate 'CN=revoked' is revoked}).to_stderr
848+
end
849+
end
850+
end
743851
end

0 commit comments

Comments
 (0)