1
0
mirror of http://git.haproxy.org/git/haproxy.git/ synced 2025-04-21 06:25:43 +00:00

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 | \
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>]
Dump statistics for the given resolvers section, or all resolvers sections
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) */
int ssl_sock_get_pkey_algo(struct connection *conn, struct buffer *out)
/* fill a buffer with the algorithm and size of a public key */
static int cert_get_pkey_algo(X509 *crt, struct buffer *out)
{
struct ssl_sock_ctx *ctx;
int bits = 0;
int sig = TLSEXT_signature_anonymous;
int len = -1;
X509 *crt;
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);
if (pkey) {
bits = EVP_PKEY_bits(pkey);
@ -6914,6 +6905,24 @@ int ssl_sock_get_pkey_algo(struct connection *conn, struct buffer *out)
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) */
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
* 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,
};
/* 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 */
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 },
{ { "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 },
{ { "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 }
}};