]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: acme/cli: 'acme status' show the status acme-configured certificates
authorWilliam Lallemand <wlallemand@haproxy.com>
Tue, 6 May 2025 12:56:38 +0000 (14:56 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Tue, 6 May 2025 13:27:29 +0000 (15:27 +0200)
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.

doc/management.txt
src/acme.c

index 7850370698af8ff1e5bb7feb3de48e69f8b32c8e..f9a54402366e88813590e8bbb7a365f978930ced 100644 (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
index 098e2c593caf538935bcc538f248d1c8d7c94a9f..f888c4680119ba0ef673a2072e8617fb668bcbff 100644 (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 }
 }};