MINOR: ssl/cli: 'show ssl cert' give information on the certificates

Implement the 'show ssl cert' command on the CLI which list the frontend
certificates. With a certificate name in parameter it will show more
details.
This commit is contained in:
William Lallemand 2019-12-05 10:26:40 +01:00 committed by William Lallemand
parent 545989f37f
commit d4f946c469
2 changed files with 301 additions and 11 deletions

View File

@ -2507,6 +2507,37 @@ show stat [{<iid>|<proxy>} <type> <sid>] [typed|json] [desc]
$ echo "show stat json" | socat /var/run/haproxy.sock stdio | \ $ echo "show stat json" | socat /var/run/haproxy.sock stdio | \
python -m json.tool python -m json.tool
show ssl cert [<filename>]
Display the list of certicates used on frontends. If a filename is prefixed
by an asterisk, it is a transaction which is not commited yet. If a
filename is specified, it will show details about the certificate. This
command can be useful to check if a certificate was well updated. You can
also display details on a transaction by prefixing the filename by an
asterisk.
Example :
$ echo "@1 show ssl cert" | socat /var/run/haproxy.master -
# transaction
*test.local.pem
# filename
test.local.pem
$ echo "@1 show ssl cert test.local.pem" | socat /var/run/haproxy.master -
Filename: test.local.pem
Serial: 03ECC19BA54B25E85ABA46EE561B9A10D26F
notBefore: Sep 13 21:20:24 2019 GMT
notAfter: Dec 12 21:20:24 2019 GMT
Issuer: /C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
Subject: /CN=test.local
Subject Alternative Name: DNS:test.local, DNS:imap.test.local
Algorithm: RSA2048
SHA1 FingerPrint: 417A11CAE25F607B24F638B4A8AEE51D1E211477
$ echo "@1 show ssl cert *test.local.pem" | socat /var/run/haproxy.master -
Filename: *test.local.pem
[...]
show resolvers [<resolvers section id>] show resolvers [<resolvers section id>]
Dump statistics for the given resolvers section, or all resolvers sections Dump statistics for the given resolvers section, or all resolvers sections
if no section is supplied. if no section is supplied.

View File

@ -6862,23 +6862,14 @@ static void ssl_sock_shutw(struct connection *conn, void *xprt_ctx, int clean)
} }
} }
/* used for ppv2 pkey alog (can be used for logging) */ /* fill a buffer with the algorithm and size of a public key */
int ssl_sock_get_pkey_algo(struct connection *conn, struct buffer *out) static int cert_get_pkey_algo(X509 *crt, struct buffer *out)
{ {
struct ssl_sock_ctx *ctx;
int bits = 0; int bits = 0;
int sig = TLSEXT_signature_anonymous; int sig = TLSEXT_signature_anonymous;
int len = -1; int len = -1;
X509 *crt;
EVP_PKEY *pkey; EVP_PKEY *pkey;
if (!ssl_sock_is_ssl(conn))
return 0;
ctx = conn->xprt_ctx;
crt = SSL_get_certificate(ctx->ssl);
if (!crt)
return 0;
pkey = X509_get_pubkey(crt); pkey = X509_get_pubkey(crt);
if (pkey) { if (pkey) {
bits = EVP_PKEY_bits(pkey); bits = EVP_PKEY_bits(pkey);
@ -6914,6 +6905,24 @@ int ssl_sock_get_pkey_algo(struct connection *conn, struct buffer *out)
return 1; return 1;
} }
/* used for ppv2 pkey alog (can be used for logging) */
int ssl_sock_get_pkey_algo(struct connection *conn, struct buffer *out)
{
struct ssl_sock_ctx *ctx;
X509 *crt;
if (!ssl_sock_is_ssl(conn))
return 0;
ctx = conn->xprt_ctx;
crt = SSL_get_certificate(ctx->ssl);
if (!crt)
return 0;
return cert_get_pkey_algo(crt, out);
}
/* used for ppv2 cert signature (can be used for logging) */ /* used for ppv2 cert signature (can be used for logging) */
const char *ssl_sock_get_cert_sig(struct connection *conn) const char *ssl_sock_get_cert_sig(struct connection *conn)
{ {
@ -7113,6 +7122,36 @@ ssl_sock_get_dn_entry(X509_NAME *a, const struct buffer *entry, int pos,
} }
/*
* Extract and format the DNS SAN extensions and copy result into a chuink
* Return 0;
*/
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
static int ssl_sock_get_san_oneline(X509 *cert, struct buffer *out)
{
int i;
char *str;
STACK_OF(GENERAL_NAME) *names = NULL;
names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
if (names) {
for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
if (i > 0)
chunk_appendf(out, ", ");
if (name->type == GEN_DNS) {
if (ASN1_STRING_to_UTF8((unsigned char **)&str, name->d.dNSName) >= 0) {
chunk_appendf(out, "DNS:%s", str);
OPENSSL_free(str);
}
}
}
sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
}
return 0;
}
#endif
/* Extract and format full DN from a X509_NAME and copy result into a chunk /* Extract and format full DN from a X509_NAME and copy result into a chunk
* Returns 1 if dn entries exits, 0 if no dn entry found or -1 if output is not large enough. * Returns 1 if dn entries exits, 0 if no dn entry found or -1 if output is not large enough.
*/ */
@ -10137,6 +10176,225 @@ enum {
SETCERT_ST_FIN, SETCERT_ST_FIN,
}; };
/* release function of the `show ssl cert' command */
static void cli_release_show_cert(struct appctx *appctx)
{
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
}
/* IO handler of "show ssl cert <filename>" */
static int cli_io_handler_show_cert(struct appctx *appctx)
{
struct buffer *trash = alloc_trash_chunk();
struct ebmb_node *node;
struct stream_interface *si = appctx->owner;
struct ckch_store *ckchs;
int n;
if (trash == NULL)
return 1;
if (!appctx->ctx.ssl.old_ckchs) {
if (ckchs_transaction.old_ckchs) {
ckchs = ckchs_transaction.old_ckchs;
chunk_appendf(trash, "# transaction\n");
if (!ckchs->multi) {
chunk_appendf(trash, "*%s\n", ckchs->path);
} else {
chunk_appendf(trash, "*%s:", ckchs->path);
for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
if (ckchs->ckch[n].cert)
chunk_appendf(trash, " %s.%s\n", ckchs->path, SSL_SOCK_KEYTYPE_NAMES[n]);
}
chunk_appendf(trash, "\n");
}
}
}
if (!appctx->ctx.cli.p0) {
chunk_appendf(trash, "# filename\n");
node = ebmb_first(&ckchs_tree);
} else {
node = &((struct ckch_store *)appctx->ctx.cli.p0)->node;
}
while (node) {
ckchs = ebmb_entry(node, struct ckch_store, node);
if (!ckchs->multi) {
chunk_appendf(trash, "%s\n", ckchs->path);
} else {
chunk_appendf(trash, "%s:", ckchs->path);
for (n = 0; n < SSL_SOCK_NUM_KEYTYPES; n++) {
if (ckchs->ckch[n].cert)
chunk_appendf(trash, " %s.%s", ckchs->path, SSL_SOCK_KEYTYPE_NAMES[n]);
}
chunk_appendf(trash, "\n");
}
node = ebmb_next(node);
if (ci_putchk(si_ic(si), trash) == -1) {
si_rx_room_blk(si);
goto yield;
}
}
appctx->ctx.cli.p0 = NULL;
free_trash_chunk(trash);
return 1;
yield:
free_trash_chunk(trash);
appctx->ctx.cli.p0 = ckchs;
return 0; /* should come back */
}
/* IO handler of the details "show ssl cert <filename>" */
static int cli_io_handler_show_cert_detail(struct appctx *appctx)
{
struct stream_interface *si = appctx->owner;
struct ckch_store *ckchs = appctx->ctx.cli.p0;
struct buffer *out = alloc_trash_chunk();
struct buffer *tmp = alloc_trash_chunk();
X509_NAME *name = NULL;
int write = -1;
BIO *bio = NULL;
if (!tmp || !out)
goto end;
if (!ckchs->multi) {
chunk_appendf(out, "Filename: ");
if (ckchs == ckchs_transaction.new_ckchs)
chunk_appendf(out, "*");
chunk_appendf(out, "%s\n", ckchs->path);
chunk_appendf(out, "Serial: ");
if (ssl_sock_get_serial(ckchs->ckch->cert, tmp) == -1)
goto end;
dump_binary(out, tmp->area, tmp->data);
chunk_appendf(out, "\n");
chunk_appendf(out, "notBefore: ");
chunk_reset(tmp);
if ((bio = BIO_new(BIO_s_mem())) == NULL)
goto end;
if (ASN1_TIME_print(bio, X509_getm_notBefore(ckchs->ckch->cert)) == 0)
goto end;
write = BIO_read(bio, tmp->area, tmp->size-1);
tmp->area[write] = '\0';
BIO_free(bio);
chunk_appendf(out, "%s\n", tmp->area);
chunk_appendf(out, "notAfter: ");
chunk_reset(tmp);
if ((bio = BIO_new(BIO_s_mem())) == NULL)
goto end;
if (ASN1_TIME_print(bio, X509_getm_notAfter(ckchs->ckch->cert)) == 0)
goto end;
if ((write = BIO_read(bio, tmp->area, tmp->size-1)) <= 0)
goto end;
tmp->area[write] = '\0';
BIO_free(bio);
chunk_appendf(out, "%s\n", tmp->area);
chunk_appendf(out, "Issuer: ");
if ((name = X509_get_issuer_name(ckchs->ckch->cert)) == NULL)
goto end;
if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
goto end;
*(tmp->area + tmp->data) = '\0';
chunk_appendf(out, "%s\n", tmp->area);
chunk_appendf(out, "Subject: ");
if ((name = X509_get_subject_name(ckchs->ckch->cert)) == NULL)
goto end;
if ((ssl_sock_get_dn_oneline(name, tmp)) == -1)
goto end;
*(tmp->area + tmp->data) = '\0';
chunk_appendf(out, "%s\n", tmp->area);
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
chunk_appendf(out, "Subject Alternative Name: ");
if (ssl_sock_get_san_oneline(ckchs->ckch->cert, out) == -1)
goto end;
*(out->area + out->data) = '\0';
chunk_appendf(out, "\n");
#endif
chunk_reset(tmp);
chunk_appendf(out, "Algorithm: ");
if (cert_get_pkey_algo(ckchs->ckch->cert, tmp) == 0)
goto end;
chunk_appendf(out, "%s\n", tmp->area);
chunk_reset(tmp);
chunk_appendf(out, "SHA1 FingerPrint: ");
if (X509_digest(ckchs->ckch->cert, EVP_sha1(), (unsigned char *) tmp->area,
(unsigned int *)&tmp->data) == 0)
goto end;
dump_binary(out, tmp->area, tmp->data);
chunk_appendf(out, "\n");
}
if (ci_putchk(si_ic(si), out) == -1) {
si_rx_room_blk(si);
goto yield;
}
end:
free_trash_chunk(tmp);
free_trash_chunk(out);
return 1;
yield:
free_trash_chunk(tmp);
free_trash_chunk(out);
return 0; /* should come back */
}
/* parsing function for 'show ssl cert [certfile]' */
static int cli_parse_show_cert(char **args, char *payload, struct appctx *appctx, void *private)
{
struct ckch_store *ckchs;
if (!cli_has_level(appctx, ACCESS_LVL_OPER))
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))
goto error;
} else {
if ((ckchs = ckchs_lookup(args[3])) == NULL)
goto error;
}
if (ckchs->multi)
goto error;
appctx->ctx.cli.p0 = ckchs;
/* use the IO handler that shows details */
appctx->io_handler = cli_io_handler_show_cert_detail;
}
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");
}
/* release function of the `set ssl cert' command, free things and unlock the spinlock */ /* release function of the `set ssl cert' command, free things and unlock the spinlock */
static void cli_release_commit_cert(struct appctx *appctx) static void cli_release_commit_cert(struct appctx *appctx)
{ {
@ -10859,6 +11117,7 @@ static struct cli_kw_list cli_kws = {{ },{
{ { "set", "ssl", "cert", NULL }, "set ssl cert <certfile> <payload> : replace a certificate file", cli_parse_set_cert, NULL, NULL }, { { "set", "ssl", "cert", NULL }, "set ssl cert <certfile> <payload> : replace a certificate file", cli_parse_set_cert, NULL, NULL },
{ { "commit", "ssl", "cert", NULL }, "commit ssl cert <certfile> : commit a certificate file", cli_parse_commit_cert, cli_io_handler_commit_cert, cli_release_commit_cert }, { { "commit", "ssl", "cert", NULL }, "commit ssl cert <certfile> : commit a certificate file", cli_parse_commit_cert, cli_io_handler_commit_cert, cli_release_commit_cert },
{ { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL }, { { "abort", "ssl", "cert", NULL }, "abort ssl cert <certfile> : abort a transaction for a certificate file", cli_parse_abort_cert, NULL, NULL },
{ { "show", "ssl", "cert", NULL }, "show ssl cert [<certfile>] : display the SSL certificates used in memory, or the details of a <certfile>", cli_parse_show_cert, cli_io_handler_show_cert, cli_release_show_cert },
{ { NULL }, NULL, NULL, NULL } { { NULL }, NULL, NULL, NULL }
}}; }};