{"record":{"id":"d5822111f0ec2471","repo":"puppetlabs/puppet","slug":"ssl-context-must-contain-a-client-certificate","errorCode":null,"errorMessage":"SSL context must contain a client certificate.","messagePattern":"SSL context must contain a client certificate\\.","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/http/service/ca.rb","lineNumber":127,"sourceCode":"  # Submit a POST request to send a certificate renewal request to the server\n  #\n  # @param [Puppet::SSL::SSLContext] ssl_context\n  #\n  # @return [Array<Puppet::HTTP::Response, String>] The request response\n  #\n  # @api public\n  def post_certificate_renewal(ssl_context)\n    headers = add_puppet_headers(HEADERS)\n    headers['Content-Type'] = 'text/plain'\n\n    response = @client.post(\n      with_base_url('/certificate_renewal'),\n      '', # Puppet::HTTP::Client.post requires a body, the API endpoint does not\n      headers: headers,\n      options: { ssl_context: ssl_context }\n    )\n\n    raise ArgumentError, _('SSL context must contain a client certificate.') unless ssl_context.client_cert\n\n    process_response(response)\n\n    [response, response.body.to_s]\n  end\nend\n","sourceCodeStart":109,"sourceCodeEnd":134,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/http/service/ca.rb#L109-L134","documentation":"Raised by Puppet::HTTP::Service::Ca#post_certificate_renewal when the ssl_context passed to the call has no client certificate (ssl_context.client_cert is nil). Certificate renewal is the CA operation where an already-certified agent asks for a new certificate, so the request must authenticate with the existing client cert. Note the check runs after the POST is issued, so a request has already gone on the wire by the time the ArgumentError surfaces.","triggerScenarios":"Calling client.post_certificate_renewal(ssl_context) with a context built without a client cert — e.g. Puppet::SSL::Provider#create_context called with only :cacerts/:crls and no :client_cert/:private_key, or a CA-chain-only context instead of one for the full agent chain.","commonSituations":"Running certificate renewal on a node that never obtained a signed certificate (renewal used where submit_certificate_request is the right enrollment path); custom tooling that hand-builds an SSLContext and forgets the client cert pair; the agent's cert files were cleaned (puppet certificate clean / ssl dir wiped) but renewal logic still runs.","solutions":["Load the context for the full agent chain from the SSL directory (Puppet::SSL::Provider#load_context) so client_cert is populated, and only call renewal on nodes that already hold a signed cert","If the node has no certificate yet, switch to CSR submission (put_certificate_request) instead of renewal","When building a context manually, pass client_cert: and private_key: to create_context","Verify cert.pem and private_key.pem exist under the agent's ssl directory before invoking the CA service"],"exampleFix":"# before (ruby)\nssl_context = provider.create_context(cacerts: [ca_cert], crls: [crl])\nclient.post_certificate_renewal(ssl_context) # => ArgumentError\n\n# after\nssl_context = provider.create_context(\n  cacerts: [ca_cert], crls: [crl],\n  client_cert: agent_cert, private_key: agent_key\n)\nclient.post_certificate_renewal(ssl_context)","handlingStrategy":"validation","validationCode":"# ruby\nraise ArgumentError, 'ssl_context has no client certificate' if ssl_context.client_cert.nil?\nclient.post_certificate_renewal(ssl_context)","typeGuard":"def has_client_cert?(ctx)\n  ctx.respond_to?(:client_cert) && !ctx.client_cert.nil?\nend","tryCatchPattern":"begin\n  client.post_certificate_renewal(ssl_context)\nrescue ArgumentError => e\n  raise unless e.message.include?('client certificate')\n  client.put_certificate_request(certname, csr)  # fall back to enrollment\nend","preventionTips":["Obtain request contexts via Puppet::SSL::Provider#load_context rather than hand-building them","Check ssl_context.client_cert before any CA call that authenticates the agent","Gate renewal flows on the existence of $ssldir/certs/<certname>.pem before invoking them"],"tags":["ssl","client-certificate","certificate-renewal","puppet-ca","argument-error"],"backgroundTag":"ssl-client-certificate-missing","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}