MINOR: acme/cli: 'acme status' show the status acme-configured certificates

The "acme status" command, shows the status of every certificates
configured with ACME, not only the running task like "acme ps".

The IO handler loops on the ckch_store tree and outputs a line for each
ckch_store which has an acme section set. This is still done under the
ckch_store lock and doesn't support resuming when the buffer is full,
but we need to change that in the future.
This commit is contained in:
William Lallemand 2025-05-06 14:56:38 +02:00
parent a3ce7d7772
commit 48f1ce77b7
2 changed files with 107 additions and 0 deletions

View File

@ -1655,6 +1655,24 @@ acme renew <certificate>
The certificate must be linked to an acme section, see section 3.13. of the
configuration manual. See also "acme ps".
acme status
Show the status of every certificates that were configured with ACME.
This command outputs, separated by a tab:
- The name of the certificate configured in haproxy
- The acme section used in the configuration
- The state of the acme task, either "Running" or "Scheduled"
- The UTC expiration date of the certificate in ISO8601 format
- The relative expiration time (0d if expired)
- The UTC expiration date of the certificate in ISO8601 format
- The relative schedule time (0d if Running)
Example:
$ echo "@1; acme status" | socat /tmp/master.sock - | column -t -s $'\t'
# certificate section state expiration date (UTC) expires in scheduled date (UTC) scheduled in
ecdsa.pem LE Running 2020-01-18T09:31:12Z 0d 0h00m00s 2020-01-15T21:31:12Z 0d 0h00m00s
foobar.pem.rsa LE Scheduled 2025-08-04T11:50:54Z 89d 23h01m13s 2025-07-27T23:50:55Z 82d 11h01m14s
add acl [@<ver>] <acl> <pattern>
Add an entry into the acl <acl>. <acl> is the #<id> or the <name> returned by
"show acl". This command does not verify if the entry already exists. Entries

View File

@ -2044,6 +2044,32 @@ int acme_will_expire(struct ckch_store *store)
return 0;
}
/*
* Return when the next task is scheduled
* Check if the notAfter date will happen in (validity period / 12) or 7 days per default
*/
time_t acme_schedule_date(struct ckch_store *store)
{
int diff = 0;
time_t notAfter = 0;
time_t notBefore = 0;
/* compute the validity period of the leaf certificate */
if (!store->data || !store->data->cert)
return 0;
notAfter = x509_get_notafter_time_t(store->data->cert);
notBefore = x509_get_notbefore_time_t(store->data->cert);
if (notAfter >= 0 && notBefore >= 0) {
diff = (notAfter - notBefore) / 12; /* validity period / 12 */
} else {
diff = 7 * 24 * 60 * 60; /* default to 7 days */
}
return (notAfter - diff);
}
/* Does the scheduling of the ACME tasks
*/
struct task *acme_scheduler(struct task *task, void *context, unsigned int state)
@ -2319,6 +2345,68 @@ static int cli_acme_ps_io_handler(struct appctx *appctx)
return 1;
}
static int cli_acme_status_io_handler(struct appctx *appctx)
{
struct ebmb_node *node = NULL;
struct ckch_store *store = NULL;
if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
return 1;
chunk_reset(&trash);
chunk_appendf(&trash, "# certificate\tsection\tstate\texpiration date (UTC)\texpires in\tscheduled date (UTC)\tscheduled in\n");
if (applet_putchk(appctx, &trash) == -1)
return 1;
if (applet_putchk(appctx, &trash) == -1)
return 1;
/* TODO: handle backref list when list of task > buffer size */
node = ebmb_first(&ckchs_tree);
while (node) {
store = ebmb_entry(node, struct ckch_store, node);
if (store->conf.acme.id) {
char str[50] = {};
char *state = "Scheduled";
time_t notAfter = 0;
time_t sched = 0;
time_t remain = 0;
if (store->acme_task)
state = "Running";
chunk_appendf(&trash, "%s\t%s\t%s\t", store->path, store->conf.acme.id, state);
notAfter = x509_get_notafter_time_t(store->data->cert);
/* Expiration time */
if (notAfter > date.tv_sec)
remain = notAfter - date.tv_sec;
strftime(str, sizeof(str), "%Y-%m-%dT%H:%M:%SZ", gmtime(&notAfter));
chunk_appendf(&trash, "%s\t", str);
chunk_appendf(&trash, "%lud %luh%02lum%02lus\t", remain / 86400, (remain % 86400) / 3600, (remain % 3600) / 60, (remain % 60));
/* Scheduled time */
remain = 0;
sched = acme_schedule_date(store);
if (sched > date.tv_sec)
remain = sched - date.tv_sec;
strftime(str, sizeof(str), "%Y-%m-%dT%H:%M:%SZ", gmtime(&sched));
chunk_appendf(&trash, "%s\t", str);
chunk_appendf(&trash, "%lud %luh%02lum%02lus\n", remain / 86400, (remain % 86400) / 3600, (remain % 3600) / 60, (remain % 60));
if (applet_putchk(appctx, &trash) == -1)
return 1;
}
node = ebmb_next(node);
}
end:
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
return 1;
}
static int cli_acme_ps(char **args, char *payload, struct appctx *appctx, void *private)
{
return 0;
@ -2329,6 +2417,7 @@ static int cli_acme_ps(char **args, char *payload, struct appctx *appctx, void *
static struct cli_kw_list cli_kws = {{ },{
{ { "acme", "renew", NULL }, "acme renew <certfile> : renew a certificate using the ACME protocol", cli_acme_renew_parse, NULL, NULL, NULL, 0 },
{ { "acme", "ps", NULL }, "acme ps : show running ACME tasks", cli_acme_ps, cli_acme_ps_io_handler, NULL, NULL, 0 },
{ { "acme", "status", NULL }, "acme status : show status of certificates configured with ACME", cli_acme_ps, cli_acme_status_io_handler, NULL, NULL, 0 },
{ { NULL }, NULL, NULL, NULL }
}};