diff --git a/include/haproxy/acme-t.h b/include/haproxy/acme-t.h index faef5e7e5..f7ce3ce36 100644 --- a/include/haproxy/acme-t.h +++ b/include/haproxy/acme-t.h @@ -55,6 +55,7 @@ struct acme_auth { struct ist auth; /* auth URI */ struct ist chall; /* challenge URI */ struct ist token; /* token */ + int ready; /* is the challenge ready ? */ void *next; }; @@ -80,6 +81,7 @@ struct acme_ctx { X509_REQ *req; struct ist finalize; struct ist certificate; + struct task *task; struct mt_list el; }; diff --git a/src/acme.c b/src/acme.c index b67240c0e..b5e947be3 100644 --- a/src/acme.c +++ b/src/acme.c @@ -1753,6 +1753,11 @@ int acme_res_neworder(struct task *task, struct acme_ctx *ctx, char **errmsg) goto error; } + /* if the challenge is not DNS-01, consider that the challenge + * is ready because computed by HAProxy */ + if (strcasecmp(ctx->cfg->challenge, "DNS-01") != 0) + auth->ready = 1; + auth->next = ctx->auths; ctx->auths = auth; ctx->next_auth = auth; @@ -2111,6 +2116,11 @@ struct task *acme_process(struct task *task, void *context, unsigned int state) break; case ACME_CHALLENGE: if (http_st == ACME_HTTP_REQ) { + + /* if the challenge is not ready, wait to be wakeup */ + if (!ctx->next_auth->ready) + goto wait; + if (acme_req_challenge(task, ctx, ctx->next_auth, &errmsg) != 0) goto retry; } @@ -2267,8 +2277,16 @@ struct task *acme_process(struct task *task, void *context, unsigned int state) task = NULL; return task; -} +wait: + /* wait for a task_wakeup */ + ctx->http_state = ACME_HTTP_REQ; + ctx->state = st; + task->expire = TICK_ETERNITY; + + MT_LIST_UNLOCK_FULL(&ctx->el, tmp); + return task; +} /* * Return 1 if the certificate must be regenerated * Check if the notAfter date will append in (validity period / 12) or 7 days per default @@ -2534,6 +2552,7 @@ static int acme_start_task(struct ckch_store *store, char **errmsg) ctx->store = newstore; ctx->cfg = cfg; task->context = ctx; + ctx->task = task; MT_LIST_INIT(&ctx->el); MT_LIST_APPEND(&acme_tasks, &ctx->el); @@ -2586,6 +2605,55 @@ static int cli_acme_renew_parse(char **args, char *payload, struct appctx *appct return cli_dynerr(appctx, errmsg); } +static int cli_acme_chall_ready_parse(char **args, char *payload, struct appctx *appctx, void *private) +{ + char *errmsg = NULL; + const char *crt; + const char *dns; + struct mt_list back; + struct acme_ctx *ctx; + struct acme_auth *auth; + int found = 0; + + if (!*args[2] && !*args[3] && !*args[4]) { + memprintf(&errmsg, ": not enough parameters\n"); + goto err; + } + + crt = args[2]; + dns = args[4]; + + + MT_LIST_FOR_EACH_ENTRY_LOCKED(ctx, &acme_tasks, el, back) { + + if (strcmp(ctx->store->path, crt) != 0) + continue; + + auth = ctx->auths; + while (auth) { + if (strncmp(dns, auth->dns.ptr, auth->dns.len) == 0) { + if (!auth->ready) { + auth->ready = 1; + task_wakeup(ctx->task, TASK_WOKEN_MSG); + found = 1; + } else { + memprintf(&errmsg, "ACME challenge for crt \"%s\" and dns \"%s\" was already READY !\n", crt, dns); + } + break; + } + auth = auth->next; + } + } + if (!found) { + memprintf(&errmsg, "Couldn't find the ACME task using crt \"%s\" and dns \"%s\" !\n", crt, dns); + goto err; + } + + return cli_msg(appctx, LOG_INFO, "Challenge Ready!"); +err: + return cli_dynerr(appctx, errmsg); +} + static int cli_acme_status_io_handler(struct appctx *appctx) { struct ebmb_node *node = NULL; @@ -2668,6 +2736,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 : renew a certificate using the ACME protocol", cli_acme_renew_parse, NULL, 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 }, + { { "acme", "challenge_ready", NULL }, "acme challenge_ready domain : show status of certificates configured with ACME", cli_acme_chall_ready_parse, NULL, NULL, NULL, 0 }, { { NULL }, NULL, NULL, NULL } }};