Skip to content

Instantly share code, notes, and snippets.

@bzed
Created January 9, 2019 12:15
Show Gist options
  • Save bzed/e1e6c76e98225f997062c57aacecd2fd to your computer and use it in GitHub Desktop.
Save bzed/e1e6c76e98225f997062c57aacecd2fd to your computer and use it in GitHub Desktop.
haproxy pem formatted ssl client cert fetch - ssl_c_pem - like ssl_c_der
require("base64")
core.register_fetches("ssl_c_pem", function(txn)
local der = txn.f:ssl_c_der()
local wrap = ('.'):rep(64);
local envelope = "-----BEGIN %s-----\n%s\n-----END %s-----\n"
local typ = "CERTIFICATE";
der = base64.encode(data);
return string.format(envelope, typ, der:gsub(wrap, '%0\n', (#der-1)/64), typ);
end)
@bzed
Copy link
Author

bzed commented Apr 20, 2021

That basically means that it can't find the base64 functions.

Maybe try

local base64 = require'base64'

somewhere. probably in the function?

@helavatar
Copy link

That basically means that it can't find the base64 functions.

Maybe try

local base64 = require'base64'

somewhere. probably in the function?

I forgot to mention that I installed base64 from luarocks:
https://luarocks.org/modules/iskolbin/base64

Before that I had this error message:

[ALERT] 108/233244 (20196) : parsing [/etc/haproxy/haproxy.conf:14] : lua runtime error: /etc/haproxy/ssl_c_der.lua:1: module 'base64' not found:
no field package.preload['base64']
no file '/usr/share/lua/5.3/base64.lua'
no file '/usr/share/lua/5.3/base64/init.lua'
no file '/usr/lib64/lua/5.3/base64.lua'
no file '/usr/lib64/lua/5.3/base64/init.lua'
no file './base64.lua'
no file './base64/init.lua'
no file '/usr/lib64/lua/5.3/base64.so'
no file '/usr/lib64/lua/5.3/loadall.so'
no file './base64.so'

@gkatev
Copy link

gkatev commented Sep 22, 2021

Where does the 'data' variable on line 9 get defined?

@rmb938
Copy link

rmb938 commented Feb 16, 2025

For folks finding this many years later, here is a working example

function client_cert(txn, t)
  local der = txn.f:ssl_c_der()
  local chain_der = txn.f:ssl_c_chain_der()

  if type(der) == 'nil' then
    return
  end

  local pem_w_chain = "-----BEGIN CERTIFICATE-----"
  pem_w_chain = pem_w_chain .. "\n"
  pem_w_chain = pem_w_chain .. txn.c:base64(der)
  pem_w_chain = pem_w_chain .. "\n"
  pem_w_chain = pem_w_chain .. "-----END CERTIFICATE-----"

  pem_w_chain = txn.c:url_enc(pem_w_chain, "query")

  if t == "req" then
    txn.http:req_add_header("X-SSL-Client-Cert", pem_w_chain)
  end
  if t == "res" then
    txn.http:res_add_header("X-SSL-Client-Cert", pem_w_chain)
  end

  if type(chain_der) ~= 'nil' then
    local pem_w_chain = "-----BEGIN CERTIFICATE-----"
    pem_w_chain = pem_w_chain .. "\n"
    pem_w_chain = pem_w_chain .. txn.c:base64(chain_der)
    pem_w_chain = pem_w_chain .. "\n"
    pem_w_chain = pem_w_chain .. "-----END CERTIFICATE-----"

    pem_w_chain = txn.c:url_enc(pem_w_chain, "query")

    if t == "req" then
      txn.http:req_add_header("X-SSL-Client-Cert", pem_w_chain)
    end
    if t == "res" then
      txn.http:res_add_header("X-SSL-Client-Cert", pem_w_chain)
    end
  end

end

core.register_action("clientcert", { "http-req", "http-res" }, client_cert, 1)

This will add the X-SSL-Client-Cert header to the request or response with the contents of the header being URL encoded PEM of the client certificate and chain.

You can use it in your haproxy config under a frontend like so

http-request lua.clientcert "req"
http-response lua.clientcert "res"

If you are using this header in hashicorp vault for example you'll want this config on the listener

x_forwarded_for_client_cert_header          = "X-SSL-Client-Cert"
x_forwarded_for_client_cert_header_decoders = "URL,DER"

@bzed
Copy link
Author

bzed commented Feb 16, 2025

For folks finding this many years later, here is a working example

nice, thank you very much! Can't even remember which haproxy version we used back then :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment