[SERVER-217] Cannot decode Apache-formatted X-Client-Cert header Created: 2014/12/02  Updated: 2016/12/09  Resolved: 2016/12/09

Status: Closed
Project: Puppet Server
Component/s: None
Affects Version/s: SERVER 0.4.0
Fix Version/s: None

Type: New Feature Priority: Normal
Reporter: Jeremy Barlow Assignee: Unassigned
Resolution: Won't Do Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Relates
relates to SERVER-213 Cannot decode OpenSSL-formatted X-Cli... Needs Information
Template:
QA Contact: Erik Dasher

 Description   

Docs impact: This issue is referenced in the documentation/external_ssl_termination.markdown file. Once resolved, it'll need an update.

Puppet Server is intended to support the ability for the client certificate to be passed into a request to the master via an X- header – X-Client-Cert – when allow-header-cert-info is set to true. Puppet Server expects the content in the X-Client-Cert to be a URI-encoded form of the original Base-64 PEM certificate, i.e., with the newline characters from the original certificate replaced with %0A characters.

A VirtualHost configuration in an Apache server, where mod_proxy is used to proxy client requests on to a Puppet master, might inject this header with the following:

RequestHeader set X-Client-Cert %{SSL_CLIENT_S_DN}s

This results in the full content of the client certificate being included in the X-Client-Cert header in the request. Apache, however, does not URI-encode the original content of the certificate. Instead, the spaces inside of the...

-----BEGIN CERTIFICATE-----

and

-----END CERTIFICATE-----

... PEM tags are preserved and newline characters are replaced with spaces as well.

Puppet Server uses functionality from the puppetlabs/certificate-authority library to load the certificate into an X509Certificate object in memory. This library uses BouncyCastle classes to do the loading work. The BouncyCastle classes apparently do not understand spaces in the body of the PEM as being equivalent to newline characters. Any requests routed through Apache which have the X-Client-Cert header appended in the above way will result in the request failing, with the following error being written to the log and returned from the agent request:

Error 400 on SERVER: Unable to parse x-client-cert into certificate: -----END CERTIFICATE not found

In order to overcome this problem and not require further changes to be made in the upstream Apache configuration, Puppet Server could be enhanced to handle decoding the Apache spaces format back to a proper PEM format. This would need to be done in such a way that the URI-encoding method would still be supported, e.g., for non-Apache proxies. This would arguably be a "new feature" for Puppet Server, though, in that if the X-Client-Cert were sent from Apache mod_proxy to a Ruby Puppet master running under Apache/Passenger where no special decoding of the header were provided at the Apache/Passenger layer, the Ruby Puppet master would similarly be unable to handle decoding the certificate and would, therefore, fail the request.



 Comments   
Comment by banuchka [ 2015/08/14 ]

As a workaround it is possible with nginx. At first we need to compile it with "HttpSetMiscModule" (http://wiki.nginx.org/HttpSetMiscModule) and use its "set_escape_uri" based on $ssl_client_raw_cert. For example it may looks like that:

set_escape_uri $ssl_client_cert_for_puppetserver $ssl_client_raw_cert;
...
proxy_set_header X-Client-Cert $ssl_client_cert_for_puppetserver;
...

Comment by Luis Fernandez Alvarez [ 2016/06/09 ]

In case it helps someone, a workaround on HAProxy could be something like:

http-request  set-header X-Client-Cert -----BEGIN\ CERTIFICATE-----%%0A%[ssl_c_der,base64]%%0A-----END\ CERTIFICATE-----\ #

To be added in the frontend section.

Comment by Lennard Klein [ 2016/06/23 ]

If you really want to use apache, this workaround works for me. It's not pretty, so I don't recommend it if you have the option of using i.e. nginx or haproxy as commented earlier.

RequestHeader unset X-Client-Cert
RequestHeader set X-Client-Cert %{SSL_CLIENT_CERT}s "expr=%{SSL_CLIENT_CERT} != ''"
RequestHeader edit* X-Client-Cert "BEGIN CERTIFICATE" "-begin-"
RequestHeader edit* X-Client-Cert "END CERTIFICATE" "-end-"
RequestHeader edit* X-Client-Cert " " "%%0A"
RequestHeader edit* X-Client-Cert "-end-" "END%%20CERTIFICATE"
RequestHeader edit* X-Client-Cert "-begin-" "BEGIN%%20CERTIFICATE"

Edit 2016/08/18 to incorporate a different fix for the problem Alan Schwarzenberger identified below (to be able to keep it in puppetlabs-apache apache::vhosts request_headers param)

Comment by Alan Schwarzenberger [ 2016/08/17 ]

It appears in my use case (apache 2.4.16 with Puppet Server 2.2.0) that the workaround from Lennard Klein is not quite complete. It works once an agent has a certificate, but on an initial connection from a puppet agent sending a CSR, there is of course no certificate yet. It appears that environment variable SSL_CLIENT_CERT is then set to an empty string rather than not being set at all, which results in the X-Client-Cert header being set but empty, rather than not being set at all. This then results in Puppet server giving the error "No certs found in PEM read from x-client-cert"
I wrapped this in an IF statement to get it working

<If "-n '%{SSL_CLIENT_CERT}'">
  RequestHeader unset X-Client-Cert
  RequestHeader set X-Client-Cert %{SSL_CLIENT_CERT}s
  RequestHeader edit* X-Client-Cert "BEGIN CERTIFICATE" "-begin-"
  RequestHeader edit* X-Client-Cert "END CERTIFICATE" "-end-"
  RequestHeader edit* X-Client-Cert " " "%%0A"
  RequestHeader edit* X-Client-Cert "-end-" "END%%20CERTIFICATE"
  RequestHeader edit* X-Client-Cert "-begin-" "BEGIN%%20CERTIFICATE"
</If>

Generated at Fri Dec 13 08:31:28 PST 2019 using JIRA 7.7.1#77002-sha1:e75ca93d5574d9409c0630b81c894d9065296414.