MEDIUM: ssl/cli: "dump ssl cert" allow to dump a certificate in PEM format

The new "dump ssl cert" CLI command allows to dump a certificate stored
into HAProxy memory. Until now it was only possible to dump the
description of the certificate using "show ssl cert", but with this new
command you can dump the PEM content on the filesystem.

This command is only available on a admin stats socket.

$ echo "@1 dump ssl cert cert.pem" | socat /tmp/master.sock -
-----BEGIN PRIVATE KEY-----
[...]
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
This commit is contained in:
William Lallemand 2024-09-09 16:34:50 +02:00
parent 68cfb222b5
commit 021ac6a108
2 changed files with 179 additions and 0 deletions

View File

@ -2109,6 +2109,23 @@ disable server <backend>/<server>
This command is restricted and can only be issued on sockets configured for
level "admin".
dump ssl cert <certfile>
Dump a certificate loaded into HAProxy memory. This will dump the certificate
in PEM format, the private key, then the leaf certificate and finally the
chain will be dumped. You can also dump a transaction by prefixing the
filename by an asterisk.
This is useful in order to save certificates on the filesystem when it was
updated on the CLI and not on the filesystem.
This command is restricted and can only be issued on sockets configured for
level "admin".
Examples:
$ echo "dump ssl cert cert1.pem" | socat /tmp/sock1 -
$ echo "dump ssl cert cert1.pem" | socat /tmp/sock1 - | openssl storeutl -noout -text /dev/stdin
dump stats-file
Generate a stats-file which can be used to preload haproxy counters values on
startup. See "Stats-file" section for more detail.

View File

@ -86,6 +86,12 @@ struct show_cert_ctx {
int transaction;
};
/* CLI context used by "dump ssl cert" */
struct dump_cert_ctx {
struct ckch_store *ckchs;
int index;
};
/* CLI context used by "commit cert" */
struct commit_cert_ctx {
struct ckch_store *old_ckchs;
@ -2040,6 +2046,160 @@ error:
return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n");
}
/* parsing function for 'dump ssl cert <certfile>' */
static int cli_parse_dump_cert(char **args, char *payload, struct appctx *appctx, void *private)
{
struct dump_cert_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
struct ckch_store *ckchs;
if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
return cli_err(appctx, "Can't allocate memory!\n");
/* The operations on the CKCH architecture are locked so we can
* manipulate ckch_store and ckch_inst */
if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
return cli_err(appctx, "Can't show!\nOperations on certificates are currently locked!\n");
/* check if there is a certificate to lookup */
if (*args[3]) {
if (*args[3] == '*') {
if (!ckchs_transaction.new_ckchs)
goto error;
ckchs = ckchs_transaction.new_ckchs;
if (strcmp(args[3] + 1, ckchs->path) != 0)
goto error;
} else {
if ((ckchs = ckchs_lookup(args[3])) == NULL)
goto error;
}
ctx->ckchs = ckchs;
ctx->index = -2; /* -2 for pkey, -1 for cert, >= 0 for chain */
return 0;
}
error:
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n");
}
/*
* Dump a CKCH in PEM format over the CLI
* - dump the PKEY
* - dump the cert
* - dump the chain element by element
*
* Store an index to know what we need to dump at next yield
*/
static int cli_io_handler_dump_cert(struct appctx *appctx)
{
struct dump_cert_ctx *ctx = appctx->svcctx;
struct ckch_store *ckchs = ctx->ckchs;
struct buffer *out = alloc_trash_chunk();
size_t write = 0;
BIO *bio = NULL;
int index = ctx->index;
if (!out)
goto end_no_putchk;
if ((bio = BIO_new(BIO_s_mem())) == NULL)
goto end_no_putchk;
if (index == -2) {
if (BIO_reset(bio) == -1)
goto end_no_putchk;
if (!PEM_write_bio_PrivateKey(bio, ckchs->data->key, NULL, NULL, 0, 0, NULL))
goto end_no_putchk;
write = BIO_read(bio, out->area, out->size-1);
if (write == 0)
goto end_no_putchk;
out->area[write] = '\0';
out->data = write;
if (applet_putchk(appctx, out) == -1)
goto end_no_putchk;
index++;
}
if (index == -1) {
if (BIO_reset(bio) == -1)
goto end_no_putchk;
if (!PEM_write_bio_X509(bio, ckchs->data->cert))
goto end_no_putchk;
write = BIO_read(bio, out->area, out->size-1);
if (write == 0)
goto end_no_putchk;
out->area[write] = '\0';
out->data = write;
if (applet_putchk(appctx, out) == -1)
goto yield;
index++;
}
for (; index < sk_X509_num(ckchs->data->chain); index++) {
X509 *cert = sk_X509_value(ckchs->data->chain, index);
if (BIO_reset(bio) == -1)
goto end_no_putchk;
if (!PEM_write_bio_X509(bio, cert))
goto end_no_putchk;
write = BIO_read(bio, out->area, out->size-1);
if (write == 0)
goto end_no_putchk;
out->area[write] = '\0';
out->data = write;
if (applet_putchk(appctx, out) == -1)
goto yield;
}
end:
free_trash_chunk(out);
BIO_free(bio);
return 1; /* end, don't come back */
end_no_putchk:
free_trash_chunk(out);
BIO_free(bio);
return 1;
yield:
/* save the current state */
ctx->index = index;
free_trash_chunk(out);
BIO_free(bio);
return 0; /* should come back */
}
/* release function of the 'show ssl ca-file' command */
static void cli_release_dump_cert(struct appctx *appctx)
{
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
}
/* release function of the `set ssl cert' command, free things and unlock the spinlock */
static void cli_release_commit_cert(struct appctx *appctx)
{
@ -4023,6 +4183,8 @@ static struct cli_kw_list cli_kws = {{ },{
{ { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL },
{ { "del", "ssl", "cert", NULL }, "del ssl cert <certfile> : delete an unused certificate file", cli_parse_del_cert, NULL, NULL },
{ { "show", "ssl", "cert", NULL }, "show ssl cert [<certfile>] : display the SSL certificates used in memory, or the details of a file", cli_parse_show_cert, cli_io_handler_show_cert, cli_release_show_cert },
{ { "dump", "ssl", "cert", NULL }, "dump ssl cert <certfile> : dump the SSL certificates in PEM format", cli_parse_dump_cert, cli_io_handler_dump_cert, cli_release_dump_cert },
{ { "new", "ssl", "ca-file", NULL }, "new ssl ca-file <cafile> : create a new CA file to be used in a crt-list", cli_parse_new_cafile, NULL, NULL },
{ { "add", "ssl", "ca-file", NULL }, "add ssl ca-file <cafile> <payload> : add a certificate into the CA file", cli_parse_set_cafile, NULL, NULL },