diff --git a/doc/management.txt b/doc/management.txt index 7faa26ff8..360ffa181 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -2109,6 +2109,23 @@ disable server / This command is restricted and can only be issued on sockets configured for level "admin". +dump ssl cert + 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. diff --git a/src/ssl_ckch.c b/src/ssl_ckch.c index 0ca290d18..a2ba4b83e 100644 --- a/src/ssl_ckch.c +++ b/src/ssl_ckch.c @@ -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 @@ static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx return cli_err(appctx, "Can't display the certificate: Not found or the certificate is a bundle!\n"); } +/* parsing function for 'dump ssl cert ' */ +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 : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL }, { { "del", "ssl", "cert", NULL }, "del ssl cert : delete an unused certificate file", cli_parse_del_cert, NULL, NULL }, { { "show", "ssl", "cert", NULL }, "show ssl cert [] : 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 : 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 : 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 : add a certificate into the CA file", cli_parse_set_cafile, NULL, NULL },