Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net/haproxy: add OCSP stapling support #1430

Closed
fraenki opened this issue Jul 29, 2019 · 11 comments
Closed

net/haproxy: add OCSP stapling support #1430

fraenki opened this issue Jul 29, 2019 · 11 comments
Assignees
Labels
feature Adding new functionality help wanted Contributor missing

Comments

@fraenki
Copy link
Member

fraenki commented Jul 29, 2019

via https://forum.opnsense.org/index.php?topic=12978

@fraenki fraenki added the feature Adding new functionality label Jul 29, 2019
@fraenki fraenki self-assigned this Jul 29, 2019
@andrewheberle
Copy link
Contributor

andrewheberle commented Aug 9, 2019

I haven't had a chance to look into how this might be integrated into Opnsense however we use the following Ansible managed script to update the OCSP response data for HAProxy in our environment.

As HAProxy doesn't fetch OCSP data natively, this script grabs the OCSP reponse using openssl then writes it to /path/to/haproxy/ssl/cert.pem.ocsp (so HAProxy can see and serve the response after a restart) and then updates the OCSP reponse live via the stats socket (to avoid a reload/restart).

As OCSP responses have a relatively limited life (2 days I think), we run this on our load balancers via cron nightly and also as part of a renew hook for Lets Encrypt.

#!/bin/sh -e
# {{ ansible_managed }}

# Get an OSCP response from the certificates OCSP issuer for use
# with HAProxy, then update the response.

# Path to certificates
PEMSDIR={{ haproxy_config_crt_base }}
SOCKET={{ haproxy_config_stats_socket }}

cd ${PEMSDIR} || exit 1

for suffix in "" ".rsa" ".ecdsa"; do
	for pem in $(find . -maxdepth 1 -name "*.pem${suffix}" -type f -print | sed 's/^.\///' | tr '\n' ' '); do

		# Get the OCSP URL from the certificate
		ocsp_url=$(openssl x509 -noout -ocsp_uri -in "${pem}")

		# Only process OCSP if URL was present
		if [ "${ocsp_url}" != "" ]; then
			# Extract the hostname from the OCSP URL
			ocsp_host=$(echo $ocsp_url | cut -d/ -f3)

			# Only process the certificate if we have a .issuer file
			if [ -r ${pem}.issuer ]; then
				# Check if issuer cert is also a root CA cert
				subjectdn=$(openssl x509 -in "${pem}.issuer" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
				issuerdn=$(openssl x509 -in "${pem}.issuer" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
				OCSP_UPDATE=""
				if [ "${subjectdn}" == "${issuerdn}" ]; then
					# Request the OCSP response from the issuer and store it
					# and also push the response to HAProxy via the stats/config socket
					openssl ocsp \
						-issuer "${pem}.issuer" \
						-cert "${pem}" \
						-url "${ocsp_url}" \
						-header Host{{ haproxy_ocsp_update_host_seperator }}"${ocsp_host}" \
						-respout "${pem}.ocsp" \
						-verify_other "${pem}.issuer" \
						-no_nonce \
						-CAfile "${pem}.issuer" \
						| grep -q "${pem}: good" && \
					OCSP_UPDATE="$(base64 "${pem}.ocsp" | tr -d '\n')"
				else
					# Request the OCSP response from the issuer and store it
					# and also push the response to HAProxy via the stats/config socket
					openssl ocsp \
						-issuer "${pem}.issuer" \
						-cert "${pem}" \
						-url "${ocsp_url}" \
						-header Host{{ haproxy_ocsp_update_host_seperator }}"${ocsp_host}" \
						-respout "${pem}.ocsp" \
						-verify_other "${pem}.issuer" \
						-no_nonce \
						| grep -q "${pem}: good" && \
					OCSP_UPDATE="$(base64 "${pem}.ocsp" | tr -d '\n')"
				fi
				if [ "${OCSP_UPDATE}" != "" ]; then
					echo "set ssl ocsp-response ${OCSP_UPDATE}" | socat {{ haproxy_config_stats_socket }} stdio
				fi
			fi
		fi
	done
done

The following variables are used in the above script:

  • haproxy_config_crt_base: Where the certs are
  • haproxy_config_stats_socket: Where the stats socket is
  • haproxy_ocsp_update_host_seperator: Depending on the version of openssl the seperator used for the "-header" option is either a <space> or an <equals sign>

PS.

Please don't use this as-is :)

Just looking at it now there are a few assumptions made that "Work For Me ™" but may cause problems in a general sense.

@fprina
Copy link

fprina commented Aug 9, 2019

Thanks
In the next days I'll try to test/adapt your script

@fraenki fraenki added the help wanted Contributor missing label Sep 3, 2019
@fraenki
Copy link
Member Author

fraenki commented Dec 29, 2019

@Da-Juan
Copy link

Da-Juan commented May 6, 2020

I ran into this issue, here is my work around.

Hope it helps :)

I created a script base on acme.sh's haproxy deploy hook.

#!/bin/sh                          
                                   
HAPROXY_DIR="/tmp/haproxy/ssl"     
ACME_DIR="/var/etc/acme-client/home"
                                   
for _pem in "$HAPROXY_DIR"/*.pem; do
        cert_file="$(basename "$_pem")"
        _issuer="${HAPROXY_DIR}/${cert_file%.pem}.issuer"                                                                                                                                                                                                                                                                      
        _ocsp="${_pem}.ocsp"       
        cert_cn="$(openssl x509 -in "$_pem" -noout -text | grep "Subject: CN" | cut -d"=" -f2)"
        ca_file="${ACME_DIR}/${cert_cn}/ca.cer"
        if [ -f "$ca_file" ]; then 
                cp "$ca_file" "$_issuer"
        else                       
                continue           
        fi                         
                                   
        if [ -r "${_issuer}" ]; then
                _ocsp_url="$(openssl x509 -noout -ocsp_uri -in "$_pem")"
                if [ -n "$_ocsp_url" ]; then
                        _ocsp_host="$(echo "$_ocsp_url" | cut -d/ -f3)"
                        subjectdn="$(openssl x509 -in "$_issuer" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)"
                        issuerdn="$(openssl x509 -in "$_issuer" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)"
                        if [ "$subjectdn" = "$issuerdn" ]; then
                                _cafile_argument="-CAfile \"${_issuer}\""
                        else       
                                _cafile_argument=""
                        fi         
                        _openssl_version=$(openssl version | cut -d' ' -f2)
                        _openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1)
                        _openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2)
                        if [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
                                _header_sep="="
                        else       
                                _header_sep=" "
                        fi         
                                   
                        _openssl_ocsp_cmd="openssl ocsp \
                                -issuer \"${_issuer}\" \
                                -cert \"${_pem}\" \
                                -url \"${_ocsp_url}\" \
                                -header Host${_header_sep}\"${_ocsp_host}\" \
                                -respout \"${_ocsp}\" \
                                -verify_other \"${_issuer}\" \
                                ${_cafile_argument} \
                                | grep -q \"${_pem}: good\""
                                   
                        eval "${_openssl_ocsp_cmd}"
                        _ret=$?    
                                   
                        if [ "${_ret}" != "0" ]; then
                                _err "Updating OCSP stapling failed with return code ${_ret}"
                        fi         
                fi                 
        fi                         
done                               
                                   
/usr/local/etc/rc.d/haproxy reload

Then created an action (/usr/local/opnsense/service/conf/actions_oscp.conf) so I can configure it as a cron command.

[update]                           
command:/home/scripts/update_ocsp.sh
parameters:                        
type:script                        
message:updating OCSP responses    
description:update Let's Encrypt certificates OCSP responses for HAProxy

@xma-kultapanda
Copy link

Thanks @Da-Juan that worked nearly flawlessly.

In my case I had to trim the output from your CommonName extraction as it returned a leading char.

cert_cn="$(openssl x509 -in "$_pem" -noout -text | grep "Subject: CN" | cut -d"=" -f2)"
cert_cn="$(openssl x509 -in "$_pem" -noout -text | grep "Subject: CN" | cut -d"=" -f2 | tr -d '\040\011\012\015')"

@Da-Juan
Copy link

Da-Juan commented Sep 14, 2020

Yes I ran in the same issue, I updated my script with this:

cert_cn="$(openssl x509 -in "$_pem" -noout -text | sed -nE 's/.*Subject:.*CN = ([^,]*)(,.*)?$/\1/p')"

I saved a Gist here: https://gist.github.com/Da-Juan/0f765160e69a99882c5188b6ab4e13e1

@xma-kultapanda
Copy link

Yeah, that's more elegant.
Thank you.

@fraenki
Copy link
Member Author

fraenki commented Sep 19, 2020

Does anybody care to properly integrate it and submit a pull-request? :)

fraenki added a commit to fraenki/plugins that referenced this issue Feb 15, 2021
@fraenki
Copy link
Member Author

fraenki commented Feb 15, 2021

The upcoming os-haproxy version 3.0 will include basic support for OCSP stapling.

It needs to be enabled in Services: HAProxy: Settings (Settings -> Service) and a cron job to update the OCSP data needs to be added in System: Settings: Cron. A future update may make the cron job obsolete (by using HAProxy's socket to update the OCSP data), but we need to start somewhere.

I cannot provide a patch, because it will likely fail to apply against os-haproxy version 2.x, but here's the commit for the fearless to test it: 9c036d2. Feedback would be much appreciated.

@Arnavion
Copy link
Contributor

@fraenki It seems your implementation only works for certs issued via an internal CA, because it relies on $cert->caref which is only set for those certs.

I use imported certs where the issuer is appended to the PEM of the server cert. In this case there is no caref, so no .issuer file is exported into /tmp/haproxy/ssl, so updateOcsp.sh does nothing.

Do you want to reopen this issue? Or should I open a new one?

@fraenki
Copy link
Member Author

fraenki commented Jun 13, 2021

@Arnavion Please report a new bug, I'll lock this conversation now.

@opnsense opnsense locked as resolved and limited conversation to collaborators Jun 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature Adding new functionality help wanted Contributor missing
Development

No branches or pull requests

6 participants