Skip to content

Commit

Permalink
(PUP-11856) State machine renews host cert
Browse files Browse the repository at this point in the history
This commit adds a new NeedRenewedCert class and related logic to the
state machine to handle automatic host/client certificate renewal.
  • Loading branch information
mhashizume committed Jun 26, 2023
1 parent 96528b4 commit d459368
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 2 deletions.
44 changes: 42 additions & 2 deletions lib/puppet/ssl/state_machine.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative '../../puppet/ssl'
require_relative '../../puppet/util/pidlock'
require 'debug'

# This class implements a state machine for bootstrapping a host's CA and CRL
# bundles, private key and signed client certificate. Each state has a frozen
Expand Down Expand Up @@ -262,7 +263,11 @@ def next_state
next_ctx = @ssl_provider.create_context(
cacerts: @ssl_context.cacerts, crls: @ssl_context.crls, private_key: key, client_cert: cert
)
return Done.new(@machine, next_ctx)
if needs_refresh?(cert)
NeedRenewedCert.new(@machine, next_ctx, key)
else
return Done.new(@machine, next_ctx)
end
end
else
if Puppet[:key_type] == 'ec'
Expand All @@ -278,6 +283,15 @@ def next_state

NeedSubmitCSR.new(@machine, @ssl_context, key)
end

private

def needs_refresh?(cert)
cert_ttl = Puppet[:hostcert_renewal_interval]
return false unless cert_ttl

Time.now.to_i >= (cert.not_after.to_i - cert_ttl)
end
end

# Base class for states with a private key.
Expand Down Expand Up @@ -349,6 +363,32 @@ def next_state
end
end

# Class to renew a client/host certificate automatically.
#
class NeedRenewedCert < KeySSLState
def next_state
Puppet.debug(_("Renewing client certificate"))

route = @machine.session.route_to(:ca, ssl_context: @ssl_context)
_, cert = route.post_certificate_renewal(@ssl_context)

# verify client cert before saving
next_ctx = @ssl_provider.create_context(
cacerts: @ssl_context.cacerts, crls: @ssl_context.crls, private_key: @private_key, client_cert: cert
)
@cert_provider.save_client_cert(Puppet[:certname], cert)

Done.new(@machine, next_ctx) if next_ctx
rescue Puppet::HTTP::ResponseError => e
if e.response.code == 404
Puppet.info(_("Certificate autorenewal has not been enabled on the server."))
else
Puppet.warning(_("Failed to automatically renew certificate: %{message}") % { message: e.response.message }, e)
Done.new(@machine, @ssl_context)
end
end
end

# We cannot make progress, so wait if allowed to do so, or exit.
#
class Wait < SSLState
Expand Down Expand Up @@ -500,7 +540,7 @@ def ensure_ca_certificates
final_state.ssl_context
end

# Run the state machine for CA certs and CRLs.
# Run the state machine for a client/host certificate.
#
# @return [Puppet::SSL::SSLContext] initialized SSLContext
# @raise [Puppet::Error] If we fail to generate an SSLContext
Expand Down
19 changes: 19 additions & 0 deletions spec/unit/ssl/state_machine_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,25 @@ def expect_lockfile_to_contain(pid)
state.next_state
}.to raise_error(OpenSSL::PKey::RSAError)
end

it "transitions to Done if current time plus renewal interval is less than cert's \"NotAfter\" time" do
allow(cert_provider).to receive(:load_private_key).and_return(private_key)
allow(cert_provider).to receive(:load_client_cert).and_return(client_cert)

st = state.next_state
expect(st).to be_instance_of(Puppet::SSL::StateMachine::Done)
end

it "returns NeedRenewedCert if current time plus renewal interval is greater than cert's \"NotAfter\" time" do
client_cert.not_after=(Time.now + 300)
allow(cert_provider).to receive(:load_private_key).and_return(private_key)
allow(cert_provider).to receive(:load_client_cert).and_return(client_cert)
ssl_context = Puppet::SSL::SSLContext.new(cacerts: [cacert], client_cert: client_cert, crls: [crl])
state = Puppet::SSL::StateMachine::NeedKey.new(machine, ssl_context)

st = state.next_state
expect(st).to be_instance_of(Puppet::SSL::StateMachine::NeedRenewedCert)
end
end

context 'in state NeedSubmitCSR' do
Expand Down

0 comments on commit d459368

Please sign in to comment.