diff options
-rw-r--r-- | changes-entries/md_auto_status.txt | 5 | ||||
-rw-r--r-- | modules/md/md_version.h | 4 | ||||
-rw-r--r-- | modules/md/mod_md_status.c | 563 | ||||
-rw-r--r-- | test/modules/md/test_920_status.py | 16 |
4 files changed, 406 insertions, 182 deletions
diff --git a/changes-entries/md_auto_status.txt b/changes-entries/md_auto_status.txt new file mode 100644 index 0000000000..34faddd4ce --- /dev/null +++ b/changes-entries/md_auto_status.txt @@ -0,0 +1,5 @@ + * Implement full auto status ("key: value" type status output). + Especially not only status summary counts for certificates and + OCSP stapling but also lists. Auto status format is similar to + what was used for mod_proxy_balancer. + [Rainer Jung] diff --git a/modules/md/md_version.h b/modules/md/md_version.h index ae723f621f..699d55107e 100644 --- a/modules/md/md_version.h +++ b/modules/md/md_version.h @@ -27,7 +27,7 @@ * @macro * Version number of the md module as c string */ -#define MOD_MD_VERSION "2.4.10" +#define MOD_MD_VERSION "2.4.12" /** * @macro @@ -35,7 +35,7 @@ * release. This is a 24 bit number with 8 bits for major number, 8 bits * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. */ -#define MOD_MD_VERSION_NUM 0x02040a +#define MOD_MD_VERSION_NUM 0x02040c #define MD_ACME_DEF_URL "https://acme-v02.api.letsencrypt.org/directory" diff --git a/modules/md/mod_md_status.c b/modules/md/mod_md_status.c index 390290b85c..59f4e0ffd7 100644 --- a/modules/md/mod_md_status.c +++ b/modules/md/mod_md_status.c @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + #include <assert.h> #include <apr_optional.h> #include <apr_time.h> @@ -55,6 +55,7 @@ #define APACHE_PREFIX "/.httpd/" #define MD_STATUS_RESOURCE APACHE_PREFIX"certificate-status" +#define HTML_STATUS(X) (!((X)->flags & AP_STATUS_SHORT)) int md_http_cert_status(request_rec *r) { @@ -66,13 +67,13 @@ int md_http_cert_status(request_rec *r) const char *keyname; apr_bucket_brigade *bb; apr_status_t rv; - + if (!r->parsed_uri.path || strcmp(MD_STATUS_RESOURCE, r->parsed_uri.path)) return DECLINED; - + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "requesting status for: %s", r->hostname); - + /* We are looking for information about a staged certificate */ sc = ap_get_module_config(r->server->module_config, &md_module); if (!sc || !sc->mc || !sc->mc->reg || !sc->mc->certificate_status_enabled) return DECLINED; @@ -84,7 +85,7 @@ int md_http_cert_status(request_rec *r) "md(%s): status supports only GET", md->name); return HTTP_NOT_IMPLEMENTED; } - + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "requesting status for MD: %s", md->name); @@ -94,7 +95,7 @@ int md_http_cert_status(request_rec *r) "loading md status for %s", md->name); return HTTP_INTERNAL_SERVER_ERROR; } - + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "status for MD: %s is %s", md->name, md_json_writep(mdj, r->pool, MD_JSON_FMT_INDENT)); @@ -124,23 +125,23 @@ int md_http_cert_status(request_rec *r) } md_json_setj(cj, resp, keyname, NULL ); } - + if (md_json_has_key(mdj, MD_KEY_RENEWAL, NULL)) { /* copy over the information we want to make public about this: * - when not finished, add an empty object to indicate something is going on * - when a certificate is staged, add the information from that */ cj = md_json_getj(mdj, MD_KEY_RENEWAL, MD_KEY_CERT, NULL); - cj = cj? cj : md_json_create(r->pool);; + cj = cj? cj : md_json_create(r->pool); md_json_setj(cj, resp, MD_KEY_RENEWAL, MD_KEY_CERT, NULL); } - + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "md[%s]: sending status", md->name); - apr_table_set(r->headers_out, "Content-Type", "application/json"); + apr_table_set(r->headers_out, "Content-Type", "application/json"); bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); md_json_writeb(resp, MD_JSON_FMT_INDENT, bb); ap_pass_brigade(r->output_filters, bb); apr_brigade_cleanup(bb); - + return DONE; } @@ -151,10 +152,12 @@ typedef struct { apr_pool_t *p; const md_mod_conf_t *mc; apr_bucket_brigade *bb; + int flags; + const char *prefix; const char *separator; } status_ctx; -typedef struct status_info status_info; +typedef struct status_info status_info; static void add_json_val(status_ctx *ctx, md_json_t *j); @@ -179,13 +182,19 @@ static void si_val_status(status_ctx *ctx, md_json_t *mdj, const status_info *in case MD_S_EXPIRED_DEPRECATED: case MD_S_COMPLETE: until = md_json_get_time(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_UNTIL, NULL); - s = (!until || until > apr_time_now())? "good" : "expired"; + s = (!until || until > apr_time_now())? "good" : "expired"; break; case MD_S_ERROR: s = "error"; break; case MD_S_MISSING_INFORMATION: s = "missing information"; break; default: break; } - apr_brigade_puts(ctx->bb, NULL, NULL, s); + if (HTML_STATUS(ctx)) { + apr_brigade_puts(ctx->bb, NULL, NULL, s); + } + else { + apr_brigade_printf(ctx->bb, NULL, NULL, "%s%s: %s\n", + ctx->prefix, info->label, s); + } } static void si_val_url(status_ctx *ctx, md_json_t *mdj, const status_info *info) @@ -195,19 +204,28 @@ static void si_val_url(status_ctx *ctx, md_json_t *mdj, const status_info *info) s = url = md_json_gets(mdj, info->key, NULL); if (!url) return; s = md_get_ca_name_from_url(ctx->p, url); - apr_brigade_printf(ctx->bb, NULL, NULL, "<a href='%s'>%s</a>", - ap_escape_html2(ctx->p, url, 1), - ap_escape_html2(ctx->p, s, 1)); + if (HTML_STATUS(ctx)) { + apr_brigade_printf(ctx->bb, NULL, NULL, "<a href='%s'>%s</a>", + ap_escape_html2(ctx->p, url, 1), + ap_escape_html2(ctx->p, s, 1)); + } + else { + apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sName: %s\n", + ctx->prefix, info->label, s); + apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sURL: %s\n", + ctx->prefix, info->label, url); + } } -static void print_date(apr_bucket_brigade *bb, apr_time_t timestamp, const char *title) +static void print_date(status_ctx *ctx, apr_time_t timestamp, const char *title) { + apr_bucket_brigade *bb = ctx->bb; if (timestamp > 0) { char ts[128]; char ts2[128]; apr_time_exp_t texp; apr_size_t len; - + apr_time_exp_gmt(&texp, timestamp); apr_strftime(ts, &len, sizeof(ts2)-1, "%Y-%m-%d", &texp); ts[len] = '\0'; @@ -216,14 +234,21 @@ static void print_date(apr_bucket_brigade *bb, apr_time_t timestamp, const char ts2[len] = '\0'; title = ts2; } - apr_brigade_printf(bb, NULL, NULL, - "<span title='%s' style='white-space: nowrap;'>%s</span>", - ap_escape_html2(bb->p, title, 1), ts); + if (HTML_STATUS(ctx)) { + apr_brigade_printf(bb, NULL, NULL, + "<span title='%s' style='white-space: nowrap;'>%s</span>", + ap_escape_html2(bb->p, title, 1), ts); + } + else { + apr_brigade_printf(bb, NULL, NULL, "%s%s: %s\n", + ctx->prefix, title, ts); + } } } -static void print_time(apr_bucket_brigade *bb, const char *label, apr_time_t t) +static void print_time(status_ctx *ctx, const char *label, apr_time_t t) { + apr_bucket_brigade *bb = ctx->bb; apr_time_t now; const char *pre, *post, *sep; char ts[APR_RFC822_DATE_LEN]; @@ -231,7 +256,7 @@ static void print_time(apr_bucket_brigade *bb, const char *label, apr_time_t t) apr_time_exp_t texp; apr_size_t len; apr_interval_time_t delta; - + if (t == 0) { /* timestamp is 0, we use that for "not set" */ return; @@ -241,25 +266,32 @@ static void print_time(apr_bucket_brigade *bb, const char *label, apr_time_t t) pre = post = ""; sep = (label && strlen(label))? " " : ""; delta = 0; - apr_rfc822_date(ts, t); - if (t > now) { - delta = t - now; - pre = "in "; - } - else { - delta = now - t; - post = " ago"; - } - if (delta >= (4 * apr_time_from_sec(MD_SECS_PER_DAY))) { - apr_strftime(ts2, &len, sizeof(ts2)-1, "%Y-%m-%d", &texp); - ts2[len] = '\0'; - apr_brigade_printf(bb, NULL, NULL, "%s%s<span title='%s' " - "style='white-space: nowrap;'>%s</span>", - label, sep, ts, ts2); + if (HTML_STATUS(ctx)) { + apr_rfc822_date(ts, t); + if (t > now) { + delta = t - now; + pre = "in "; + } + else { + delta = now - t; + post = " ago"; + } + if (delta >= (4 * apr_time_from_sec(MD_SECS_PER_DAY))) { + apr_strftime(ts2, &len, sizeof(ts2)-1, "%Y-%m-%d", &texp); + ts2[len] = '\0'; + apr_brigade_printf(bb, NULL, NULL, "%s%s<span title='%s' " + "style='white-space: nowrap;'>%s</span>", + label, sep, ts, ts2); + } + else { + apr_brigade_printf(bb, NULL, NULL, "%s%s<span title='%s'>%s%s%s</span>", + label, sep, ts, pre, md_duration_roughly(bb->p, delta), post); + } } else { - apr_brigade_printf(bb, NULL, NULL, "%s%s<span title='%s'>%s%s%s</span>", - label, sep, ts, pre, md_duration_roughly(bb->p, delta), post); + delta = t - now; + apr_brigade_printf(bb, NULL, NULL, "%s%s: %" APR_TIME_T_FMT "\n", + ctx->prefix, label, apr_time_sec(delta)); } } @@ -267,37 +299,51 @@ static void si_val_valid_time(status_ctx *ctx, md_json_t *mdj, const status_info { const char *sfrom, *suntil, *sep, *title; apr_time_t from, until; - + sep = NULL; sfrom = md_json_gets(mdj, info->key, MD_KEY_FROM, NULL); from = sfrom? apr_date_parse_rfc(sfrom) : 0; suntil = md_json_gets(mdj, info->key, MD_KEY_UNTIL, NULL); until = suntil?apr_date_parse_rfc(suntil) : 0; - - if (from > apr_time_now()) { - apr_brigade_puts(ctx->bb, NULL, NULL, "from "); - print_date(ctx->bb, from, sfrom); - sep = " "; - } - if (until) { - if (sep) apr_brigade_puts(ctx->bb, NULL, NULL, sep); - apr_brigade_puts(ctx->bb, NULL, NULL, "until "); - title = sfrom? apr_psprintf(ctx->p, "%s - %s", sfrom, suntil) : suntil; - print_date(ctx->bb, until, title); + + if (HTML_STATUS(ctx)) { + if (from > apr_time_now()) { + apr_brigade_puts(ctx->bb, NULL, NULL, "from "); + print_date(ctx, from, sfrom); + sep = " "; + } + if (until) { + if (sep) apr_brigade_puts(ctx->bb, NULL, NULL, sep); + apr_brigade_puts(ctx->bb, NULL, NULL, "until "); + title = sfrom? apr_psprintf(ctx->p, "%s - %s", sfrom, suntil) : suntil; + print_date(ctx, until, title); + } + } + else { + if (from > apr_time_now()) { + print_date(ctx, from, + apr_pstrcat(ctx->p, info->label, "From", NULL)); + } + if (until) { + print_date(ctx, from, + apr_pstrcat(ctx->p, info->label, "Until", NULL)); + } } } static void si_add_header(status_ctx *ctx, const status_info *info) { - const char *html = ap_escape_html2(ctx->p, info->label, 1); - apr_brigade_printf(ctx->bb, NULL, NULL, "<th class=\"%s\">%s</th>", html, html); + if (HTML_STATUS(ctx)) { + const char *html = ap_escape_html2(ctx->p, info->label, 1); + apr_brigade_printf(ctx->bb, NULL, NULL, "<th class=\"%s\">%s</th>", html, html); + } } static void si_val_cert_valid_time(status_ctx *ctx, md_json_t *mdj, const status_info *info) { md_json_t *jcert; status_info sub = *info; - + sub.key = MD_KEY_VALID; jcert = md_json_getj(mdj, info->key, NULL); if (jcert) si_val_valid_time(ctx, jcert, &sub); @@ -307,7 +353,7 @@ static void si_val_ca_url(status_ctx *ctx, md_json_t *mdj, const status_info *in { md_json_t *jcert; status_info sub = *info; - + sub.key = MD_KEY_URL; jcert = md_json_getj(mdj, info->key, NULL); if (jcert) si_val_url(ctx, jcert, &sub); @@ -324,83 +370,139 @@ static int count_certs(void *baton, const char *key, md_json_t *json) return 1; } -static void print_job_summary(apr_bucket_brigade *bb, md_json_t *mdj, const char *key, +static void print_job_summary(status_ctx *ctx, md_json_t *mdj, const char *key, const char *separator) { + apr_bucket_brigade *bb = ctx->bb; char buffer[HUGE_STRING_LEN]; apr_status_t rv; int finished, errors, cert_count; apr_time_t t; const char *s, *line; - + if (!md_json_has_key(mdj, key, NULL)) { return; } - + finished = md_json_getb(mdj, key, MD_KEY_FINISHED, NULL); errors = (int)md_json_getl(mdj, key, MD_KEY_ERRORS, NULL); rv = (apr_status_t)md_json_getl(mdj, key, MD_KEY_LAST, MD_KEY_STATUS, NULL); - + line = separator? separator : ""; if (rv != APR_SUCCESS) { + char *errstr = apr_strerror(rv, buffer, sizeof(buffer)); s = md_json_gets(mdj, key, MD_KEY_LAST, MD_KEY_PROBLEM, NULL); - line = apr_psprintf(bb->p, "%s Error[%s]: %s", line, - apr_strerror(rv, buffer, sizeof(buffer)), s? s : ""); + if (HTML_STATUS(ctx)) { + line = apr_psprintf(bb->p, "%s Error[%s]: %s", line, + errstr, s? s : ""); + } + else { + apr_brigade_printf(bb, NULL, NULL, "%sLastStatus: %s\n", ctx->prefix, errstr); + apr_brigade_printf(bb, NULL, NULL, "%sLastProblem: %s\n", ctx->prefix, s); + } + } + + if (!HTML_STATUS(ctx)) { + apr_brigade_printf(bb, NULL, NULL, "%sFinished: %s\n", ctx->prefix, + finished ? "yes" : "no"); } - if (finished) { cert_count = 0; md_json_iterkey(count_certs, &cert_count, mdj, key, MD_KEY_CERT, NULL); - if (cert_count > 0) { - line =apr_psprintf(bb->p, "%s finished, %d new certificate%s staged.", - line, cert_count, cert_count > 1? "s" : ""); + if (HTML_STATUS(ctx)) { + if (cert_count > 0) { + line =apr_psprintf(bb->p, "%s finished, %d new certificate%s staged.", + line, cert_count, cert_count > 1? "s" : ""); + } + else { + line = apr_psprintf(bb->p, "%s finished successfully.", line); + } } else { - line = apr_psprintf(bb->p, "%s finished successfully.", line); + apr_brigade_printf(bb, NULL, NULL, "%sNewStaged: %d\n", ctx->prefix, cert_count); } } else { s = md_json_gets(mdj, key, MD_KEY_LAST, MD_KEY_DETAIL, NULL); - if (s) line = apr_psprintf(bb->p, "%s %s", line, s); + if (s) { + if (HTML_STATUS(ctx)) { + line = apr_psprintf(bb->p, "%s %s", line, s); + } + else { + apr_brigade_printf(bb, NULL, NULL, "%sLastDetail: %s\n", ctx->prefix, s); + } + } } - + errors = (int)md_json_getl(mdj, MD_KEY_ERRORS, NULL); if (errors > 0) { - line = apr_psprintf(bb->p, "%s (%d retr%s) ", line, - errors, (errors > 1)? "y" : "ies"); - } - - apr_brigade_puts(bb, NULL, NULL, line); + if (HTML_STATUS(ctx)) { + line = apr_psprintf(bb->p, "%s (%d retr%s) ", line, + errors, (errors > 1)? "y" : "ies"); + } + else { + apr_brigade_printf(bb, NULL, NULL, "%sRetries: %d\n", ctx->prefix, errors); + } + } + + if (HTML_STATUS(ctx)) { + apr_brigade_puts(bb, NULL, NULL, line); + } t = md_json_get_time(mdj, key, MD_KEY_NEXT_RUN, NULL); if (t > apr_time_now() && !finished) { - print_time(bb, "\nNext run", t); + print_time(ctx, + HTML_STATUS(ctx) ? "\nNext run" : "NextRun", + t); } - else if (!strlen(line)) { - apr_brigade_puts(bb, NULL, NULL, "\nOngoing..."); + else if (line[0] != '\0') { + if (HTML_STATUS(ctx)) { + apr_brigade_puts(bb, NULL, NULL, "\nOngoing..."); + } + else { + apr_brigade_printf(bb, NULL, NULL, "%s: Ongoing\n", ctx->prefix); + } } } static void si_val_activity(status_ctx *ctx, md_json_t *mdj, const status_info *info) { apr_time_t t; - + const char *prefix = ctx->prefix; + (void)info; + if (!HTML_STATUS(ctx)) { + ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL); + } + if (md_json_has_key(mdj, MD_KEY_RENEWAL, NULL)) { - print_job_summary(ctx->bb, mdj, MD_KEY_RENEWAL, NULL); + print_job_summary(ctx, mdj, MD_KEY_RENEWAL, NULL); return; } - + t = md_json_get_time(mdj, MD_KEY_RENEW_AT, NULL); if (t > apr_time_now()) { - print_time(ctx->bb, "Renew", t); + print_time(ctx, "Renew", t); } else if (t) { - apr_brigade_puts(ctx->bb, NULL, NULL, "Pending"); + if (HTML_STATUS(ctx)) { + apr_brigade_puts(ctx->bb, NULL, NULL, "Pending"); + } + else { + apr_brigade_printf(ctx->bb, NULL, NULL, "%s: %s", ctx->prefix, "Pending"); + } } else if (MD_RENEW_MANUAL == md_json_getl(mdj, MD_KEY_RENEW_MODE, NULL)) { - apr_brigade_puts(ctx->bb, NULL, NULL, "Manual renew"); + if (HTML_STATUS(ctx)) { + apr_brigade_puts(ctx->bb, NULL, NULL, "Manual renew"); + } + else { + apr_brigade_printf(ctx->bb, NULL, NULL, "%s: %s", ctx->prefix, "Manual renew"); + } + } + if (!HTML_STATUS(ctx)) { + ctx->prefix = prefix; } } @@ -408,13 +510,33 @@ static int cert_check_iter(void *baton, const char *key, md_json_t *json) { status_ctx *ctx = baton; const char *fingerprint; - + fingerprint = md_json_gets(json, MD_KEY_SHA256_FINGERPRINT, NULL); if (fingerprint) { - apr_brigade_printf(ctx->bb, NULL, NULL, - "<a href=\"%s%s\">%s[%s]</a><br>", - ctx->mc->cert_check_url, fingerprint, - ctx->mc->cert_check_name, key); + if (HTML_STATUS(ctx)) { + apr_brigade_printf(ctx->bb, NULL, NULL, + "<a href=\"%s%s\">%s[%s]</a><br>", + ctx->mc->cert_check_url, fingerprint, + ctx->mc->cert_check_name, key); + } + else { + apr_brigade_printf(ctx->bb, NULL, NULL, + "%sType: %s\n", + ctx->prefix, + key); + apr_brigade_printf(ctx->bb, NULL, NULL, + "%sName: %s\n", + ctx->prefix, + ctx->mc->cert_check_name); + apr_brigade_printf(ctx->bb, NULL, NULL, + "%sURL: %s%s\n", + ctx->prefix, + ctx->mc->cert_check_url, fingerprint); + apr_brigade_printf(ctx->bb, NULL, NULL, + "%sFingerprint: %s\n", + ctx->prefix, + fingerprint); + } } return 1; } @@ -423,7 +545,14 @@ static void si_val_remote_check(status_ctx *ctx, md_json_t *mdj, const status_in { (void)info; if (ctx->mc->cert_check_name && ctx->mc->cert_check_url) { + const char *prefix = ctx->prefix; + if (!HTML_STATUS(ctx)) { + ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL); + } md_json_iterkey(cert_check_iter, ctx, mdj, MD_KEY_CERT, NULL); + if (!HTML_STATUS(ctx)) { + ctx->prefix = prefix; + } } } @@ -431,24 +560,43 @@ static void si_val_stapling(status_ctx *ctx, md_json_t *mdj, const status_info * { (void)info; if (!md_json_getb(mdj, MD_KEY_STAPLING, NULL)) return; - apr_brigade_puts(ctx->bb, NULL, NULL, "on"); + if (HTML_STATUS(ctx)) { + apr_brigade_puts(ctx->bb, NULL, NULL, "on"); + } + else { + apr_brigade_printf(ctx->bb, NULL, NULL, "%s: on", ctx->prefix); + } } static int json_iter_val(void *data, size_t index, md_json_t *json) { status_ctx *ctx = data; - if (index) apr_brigade_puts(ctx->bb, NULL, NULL, ctx->separator); + const char *prefix = ctx->prefix; + if (HTML_STATUS(ctx)) { + if (index) apr_brigade_puts(ctx->bb, NULL, NULL, ctx->separator); + } + else { + ctx->prefix = apr_pstrcat(ctx->p, prefix, apr_psprintf(ctx->p, "[%" APR_SIZE_T_FMT "]", index), NULL); + } add_json_val(ctx, json); + if (!HTML_STATUS(ctx)) { + ctx->prefix = prefix; + } return 1; } static void add_json_val(status_ctx *ctx, md_json_t *j) { if (!j) return; - else if (md_json_is(MD_JSON_TYPE_ARRAY, j, NULL)) { + if (md_json_is(MD_JSON_TYPE_ARRAY, j, NULL)) { md_json_itera(json_iter_val, ctx, j, NULL); + return; } - else if (md_json_is(MD_JSON_TYPE_INT, j, NULL)) { + if (!HTML_STATUS(ctx)) { + apr_brigade_puts(ctx->bb, NULL, NULL, ctx->prefix); + apr_brigade_puts(ctx->bb, NULL, NULL, ": "); + } + if (md_json_is(MD_JSON_TYPE_INT, j, NULL)) { md_json_writeb(j, MD_JSON_FMT_COMPACT, ctx->bb); } else if (md_json_is(MD_JSON_TYPE_STRING, j, NULL)) { @@ -460,13 +608,27 @@ static void add_json_val(status_ctx *ctx, md_json_t *j) else if (md_json_is(MD_JSON_TYPE_BOOL, j, NULL)) { apr_brigade_puts(ctx->bb, NULL, NULL, md_json_getb(j, NULL)? "on" : "off"); } + if (!HTML_STATUS(ctx)) { + apr_brigade_puts(ctx->bb, NULL, NULL, "\n"); + } } static void si_val_names(status_ctx *ctx, md_json_t *mdj, const status_info *info) { - apr_brigade_puts(ctx->bb, NULL, NULL, "<div style=\"max-width:400px;\">"); + const char *prefix = ctx->prefix; + if (HTML_STATUS(ctx)) { + apr_brigade_puts(ctx->bb, NULL, NULL, "<div style=\"max-width:400px;\">"); + } + else { + ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL); + } add_json_val(ctx, md_json_getj(mdj, info->key, NULL)); - apr_brigade_puts(ctx->bb, NULL, NULL, "</div>"); + if (HTML_STATUS(ctx)) { + apr_brigade_puts(ctx->bb, NULL, NULL, "</div>"); + } + else { + ctx->prefix = prefix; + } } static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info *info) @@ -475,7 +637,14 @@ static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info * info->fn(ctx, mdj, info); } else { + const char *prefix = ctx->prefix; + if (!HTML_STATUS(ctx)) { + ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL); + } add_json_val(ctx, md_json_getj(mdj, info->key, NULL)); + if (!HTML_STATUS(ctx)) { + ctx->prefix = prefix; + } } } @@ -486,22 +655,31 @@ static const status_info status_infos[] = { { "Valid", MD_KEY_CERT, si_val_cert_valid_time }, { "CA", MD_KEY_CA, si_val_ca_url }, { "Stapling", MD_KEY_STAPLING, si_val_stapling }, - { "Check@", MD_KEY_SHA256_FINGERPRINT, si_val_remote_check }, - { "Activity", MD_KEY_NOTIFIED, si_val_activity }, + { "CheckAt", MD_KEY_SHA256_FINGERPRINT, si_val_remote_check }, + { "Activity", MD_KEY_NOTIFIED, si_val_activity }, }; static int add_md_row(void *baton, apr_size_t index, md_json_t *mdj) { status_ctx *ctx = baton; + const char *prefix = ctx->prefix; int i; - - apr_brigade_printf(ctx->bb, NULL, NULL, "<tr class=\"%s\">", (index % 2)? "odd" : "even"); - for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) { - apr_brigade_puts(ctx->bb, NULL, NULL, "<td>"); - add_status_cell(ctx, mdj, &status_infos[i]); - apr_brigade_puts(ctx->bb, NULL, NULL, "</td>"); - } - apr_brigade_puts(ctx->bb, NULL, NULL, "</tr>"); + + if (HTML_STATUS(ctx)) { + apr_brigade_printf(ctx->bb, NULL, NULL, "<tr class=\"%s\">", (index % 2)? "odd" : "even"); + for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) { + apr_brigade_puts(ctx->bb, NULL, NULL, "<td>"); + add_status_cell(ctx, mdj, &status_infos[i]); + apr_brigade_puts(ctx->bb, NULL, NULL, "</td>"); + } + apr_brigade_puts(ctx->bb, NULL, NULL, "</tr>"); + } else { + for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) { + ctx->prefix = apr_pstrcat(ctx->p, prefix, apr_psprintf(ctx->p, "[%" APR_SIZE_T_FMT "]", index), NULL); + add_status_cell(ctx, mdj, &status_infos[i]); + ctx->prefix = prefix; + } + } return 1; } @@ -514,96 +692,121 @@ int md_domains_status_hook(request_rec *r, int flags) { const md_srv_conf_t *sc; const md_mod_conf_t *mc; - int i, html; + int i; status_ctx ctx; apr_array_header_t *mds; md_json_t *jstatus, *jstock; - + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for managed domains, start"); sc = ap_get_module_config(r->server->module_config, &md_module); if (!sc) return DECLINED; mc = sc->mc; if (!mc || !mc->server_status_enabled) return DECLINED; - html = !(flags & AP_STATUS_SHORT); ctx.p = r->pool; ctx.mc = mc; ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + ctx.flags = flags; + ctx.prefix = "ManagedCertificates"; ctx.separator = " "; mds = apr_array_copy(r->pool, mc->mds); qsort(mds->elts, (size_t)mds->nelts, sizeof(md_t *), md_name_cmp); - if (!html) { - ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "no-html summary"); - apr_brigade_puts(ctx.bb, NULL, NULL, "Managed Certificates: "); + if (!HTML_STATUS(&ctx)) { + int total = 0, complete = 0, renewing = 0, errored = 0, ready = 0; + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "no-html managed domain status summary"); if (mc->mds->nelts > 0) { md_status_take_stock(&jstock, mds, mc->reg, r->pool); - ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON summary"); - apr_brigade_printf(ctx.bb, NULL, NULL, "total=%d, ok=%d renew=%d errored=%d ready=%d", - (int)md_json_getl(jstock, MD_KEY_TOTAL, NULL), - (int)md_json_getl(jstock, MD_KEY_COMPLETE, NULL), - (int)md_json_getl(jstock, MD_KEY_RENEWING, NULL), - (int)md_json_getl(jstock, MD_KEY_ERRORED, NULL), - (int)md_json_getl(jstock, MD_KEY_READY, NULL)); - } - else { - apr_brigade_puts(ctx.bb, NULL, NULL, "[]"); + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON managed domain status summary"); + total = (int)md_json_getl(jstock, MD_KEY_TOTAL, NULL); + complete = (int)md_json_getl(jstock, MD_KEY_COMPLETE, NULL); + renewing = (int)md_json_getl(jstock, MD_KEY_RENEWING, NULL); + errored = (int)md_json_getl(jstock, MD_KEY_ERRORED, NULL); + ready = (int)md_json_getl(jstock, MD_KEY_READY, NULL); } - apr_brigade_puts(ctx.bb, NULL, NULL, "\n"); + apr_brigade_printf(ctx.bb, NULL, NULL, "%sTotal: %d\n", ctx.prefix, total); + apr_brigade_printf(ctx.bb, NULL, NULL, "%sOK: %d\n", ctx.prefix, complete); + apr_brigade_printf(ctx.bb, NULL, NULL, "%sRenew: %d\n", ctx.prefix, renewing); + apr_brigade_printf(ctx.bb, NULL, NULL, "%sErrored: %d\n", ctx.prefix, errored); + apr_brigade_printf(ctx.bb, NULL, NULL, "%sReady: %d\n", ctx.prefix, ready); } - else if (mc->mds->nelts > 0) { - ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "html table"); + if (mc->mds->nelts > 0) { md_status_get_json(&jstatus, mds, mc->reg, mc->ocsp, r->pool); - ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON status"); - apr_brigade_puts(ctx.bb, NULL, NULL, - "<hr>\n<h3>Managed Certificates</h3>\n<table class='md_status'><thead><tr>\n"); - for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) { - si_add_header(&ctx, &status_infos[i]); + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON managed domain status"); + if (HTML_STATUS(&ctx)) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "html managed domain status table"); + apr_brigade_puts(ctx.bb, NULL, NULL, + "<hr>\n<h3>Managed Certificates</h3>\n<table class='md_status'><thead><tr>\n"); + for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) { + si_add_header(&ctx, &status_infos[i]); + } + apr_brigade_puts(ctx.bb, NULL, NULL, "</tr>\n</thead><tbody>"); } - apr_brigade_puts(ctx.bb, NULL, NULL, "</tr>\n</thead><tbody>"); + else { + ctx.prefix = "ManagedDomain"; + } + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "iterating JSON managed domain status"); md_json_itera(add_md_row, &ctx, jstatus, MD_KEY_MDS, NULL); - apr_brigade_puts(ctx.bb, NULL, NULL, "</td></tr>\n</tbody>\n</table>\n"); + if (HTML_STATUS(&ctx)) { + apr_brigade_puts(ctx.bb, NULL, NULL, "</td></tr>\n</tbody>\n</table>\n"); + } } ap_pass_brigade(r->output_filters, ctx.bb); apr_brigade_cleanup(ctx.bb); ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for managed domains, end"); - + return OK; } static void si_val_ocsp_activity(status_ctx *ctx, md_json_t *mdj, const status_info *info) { apr_time_t t; - + const char *prefix = ctx->prefix; + (void)info; - t = md_json_get_time(mdj, MD_KEY_RENEW_AT, NULL); - print_time(ctx->bb, "Refresh", t); - print_job_summary(ctx->bb, mdj, MD_KEY_RENEWAL, ": "); + if (!HTML_STATUS(ctx)) { + ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL); + } + t = md_json_get_time(mdj, MD_KEY_RENEW_AT, NULL); + print_time(ctx, "Refresh", t); + print_job_summary(ctx, mdj, MD_KEY_RENEWAL, ": "); + if (!HTML_STATUS(ctx)) { + ctx->prefix = prefix; + } } static const status_info ocsp_status_infos[] = { { "Domain", MD_KEY_DOMAIN, NULL }, - { "Certificate ID", MD_KEY_ID, NULL }, - { "OCSP Status", MD_KEY_STATUS, NULL }, - { "Stapling Valid", MD_KEY_VALID, si_val_valid_time }, + { "CertificateID", MD_KEY_ID, NULL }, + { "OCSPStatus", MD_KEY_STATUS, NULL }, + { "StaplingValid", MD_KEY_VALID, si_val_valid_time }, { "Responder", MD_KEY_URL, si_val_url }, - { "Activity", MD_KEY_NOTIFIED, si_val_ocsp_activity }, + { "Activity", MD_KEY_NOTIFIED, si_val_ocsp_activity }, }; static int add_ocsp_row(void *baton, apr_size_t index, md_json_t *mdj) { status_ctx *ctx = baton; + const char *prefix = ctx->prefix; int i; - - apr_brigade_printf(ctx->bb, NULL, NULL, "<tr class=\"%s\">", (index % 2)? "odd" : "even"); - for (i = 0; i < (int)(sizeof(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) { - apr_brigade_puts(ctx->bb, NULL, NULL, "<td>"); - add_status_cell(ctx, mdj, &ocsp_status_infos[i]); - apr_brigade_puts(ctx->bb, NULL, NULL, "</td>"); - } - apr_brigade_puts(ctx->bb, NULL, NULL, "</tr>"); + + if (HTML_STATUS(ctx)) { + apr_brigade_printf(ctx->bb, NULL, NULL, "<tr class=\"%s\">", (index % 2)? "odd" : "even"); + for (i = 0; i < (int)(sizeof(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) { + apr_brigade_puts(ctx->bb, NULL, NULL, "<td>"); + add_status_cell(ctx, mdj, &ocsp_status_infos[i]); + apr_brigade_puts(ctx->bb, NULL, NULL, "</td>"); + } + apr_brigade_puts(ctx->bb, NULL, NULL, "</tr>"); + } else { + for (i = 0; i < (int)(sizeof(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) { + ctx->prefix = apr_pstrcat(ctx->p, prefix, apr_psprintf(ctx->p, "[%" APR_SIZE_T_FMT "]", index), NULL); + add_status_cell(ctx, mdj, &ocsp_status_infos[i]); + ctx->prefix = prefix; + } + } return 1; } @@ -611,53 +814,65 @@ int md_ocsp_status_hook(request_rec *r, int flags) { const md_srv_conf_t *sc; const md_mod_conf_t *mc; - int i, html; + int i; status_ctx ctx; md_json_t *jstatus, *jstock; - + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for ocsp stapling, start"); sc = ap_get_module_config(r->server->module_config, &md_module); if (!sc) return DECLINED; mc = sc->mc; if (!mc || !mc->server_status_enabled) return DECLINED; - html = !(flags & AP_STATUS_SHORT); ctx.p = r->pool; ctx.mc = mc; ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + ctx.flags = flags; + ctx.prefix = "ManagedStaplings"; ctx.separator = " "; - if (!html) { - apr_brigade_puts(ctx.bb, NULL, NULL, "Managed Staplings: "); + if (!HTML_STATUS(&ctx)) { + int total = 0, good = 0, revoked = 0, unknown = 0; + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "no-html ocsp stapling status summary"); if (md_ocsp_count(mc->ocsp) > 0) { md_ocsp_get_summary(&jstock, mc->ocsp, r->pool); - apr_brigade_printf(ctx.bb, NULL, NULL, "total=%d, good=%d revoked=%d unknown=%d", - (int)md_json_getl(jstock, MD_KEY_TOTAL, NULL), - (int)md_json_getl(jstock, MD_KEY_GOOD, NULL), - (int)md_json_getl(jstock, MD_KEY_REVOKED, NULL), - (int)md_json_getl(jstock, MD_KEY_UNKNOWN, NULL)); - } - else { - apr_brigade_puts(ctx.bb, NULL, NULL, "[]"); + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON ocsp stapling status summary"); + total = (int)md_json_getl(jstock, MD_KEY_TOTAL, NULL); + good = (int)md_json_getl(jstock, MD_KEY_GOOD, NULL); + revoked = (int)md_json_getl(jstock, MD_KEY_REVOKED, NULL); + unknown = (int)md_json_getl(jstock, MD_KEY_UNKNOWN, NULL); } - apr_brigade_puts(ctx.bb, NULL, NULL, "\n"); + apr_brigade_printf(ctx.bb, NULL, NULL, "%sTotal: %d\n", ctx.prefix, total); + apr_brigade_printf(ctx.bb, NULL, NULL, "%sOK: %d\n", ctx.prefix, good); + apr_brigade_printf(ctx.bb, NULL, NULL, "%sRenew: %d\n", ctx.prefix, revoked); + apr_brigade_printf(ctx.bb, NULL, NULL, "%sErrored: %d\n", ctx.prefix, unknown); } - else if (md_ocsp_count(mc->ocsp) > 0) { + if (md_ocsp_count(mc->ocsp) > 0) { md_ocsp_get_status_all(&jstatus, mc->ocsp, r->pool); - apr_brigade_puts(ctx.bb, NULL, NULL, - "<hr>\n<h3>Managed Staplings</h3>\n<table class='md_ocsp_status'><thead><tr>\n"); - for (i = 0; i < (int)(sizeof(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) { - si_add_header(&ctx, &ocsp_status_infos[i]); + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON ocsp stapling status"); + if (HTML_STATUS(&ctx)) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "html ocsp stapling status table"); + apr_brigade_puts(ctx.bb, NULL, NULL, + "<hr>\n<h3>Managed Staplings</h3>\n<table class='md_ocsp_status'><thead><tr>\n"); + for (i = 0; i < (int)(sizeof(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) { + si_add_header(&ctx, &ocsp_status_infos[i]); + } + apr_brigade_puts(ctx.bb, NULL, NULL, "</tr>\n</thead><tbody>"); + } + else { + ctx.prefix = "ManagedStapling"; } - apr_brigade_puts(ctx.bb, NULL, NULL, "</tr>\n</thead><tbody>"); + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "iterating JSON ocsp stapling status"); md_json_itera(add_ocsp_row, &ctx, jstatus, MD_KEY_OCSPS, NULL); - apr_brigade_puts(ctx.bb, NULL, NULL, "</td></tr>\n</tbody>\n</table>\n"); + if (HTML_STATUS(&ctx)) { + apr_brigade_puts(ctx.bb, NULL, NULL, "</td></tr>\n</tbody>\n</table>\n"); + } } ap_pass_brigade(r->output_filters, ctx.bb); apr_brigade_cleanup(ctx.bb); ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for ocsp stapling, end"); - + return OK; } @@ -687,7 +902,7 @@ int md_status_handler(request_rec *r) ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "md-status supports only GET"); return HTTP_NOT_IMPLEMENTED; } - + jstatus = NULL; md = NULL; if (r->path_info && r->path_info[0] == '/' && r->path_info[1] != '\0') { @@ -695,7 +910,7 @@ int md_status_handler(request_rec *r) md = md_get_by_name(mc->mds, name); if (!md) md = md_get_by_domain(mc->mds, name); } - + if (md) { md_status_get_md_json(&jstatus, md, mc->reg, mc->ocsp, r->pool); } @@ -706,12 +921,12 @@ int md_status_handler(request_rec *r) } if (jstatus) { - apr_table_set(r->headers_out, "Content-Type", "application/json"); + apr_table_set(r->headers_out, "Content-Type", "application/json"); bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); md_json_writeb(jstatus, MD_JSON_FMT_INDENT, bb); ap_pass_brigade(r->output_filters, bb); apr_brigade_cleanup(bb); - + return DONE; } return DECLINED; diff --git a/test/modules/md/test_920_status.py b/test/modules/md/test_920_status.py index 36c520ac94..c89ce6d8d7 100644 --- a/test/modules/md/test_920_status.py +++ b/test/modules/md/test_920_status.py @@ -148,13 +148,17 @@ Protocols h2 http/1.1 acme-tls/1 assert re.search(r'<h3>Managed Certificates</h3>', status, re.MULTILINE) # get the ascii summary status = env.get_server_status(query="?auto", via_domain=env.http_addr, use_https=False) - m = re.search(r'Managed Certificates: total=(\d+), ok=(\d+) renew=(\d+) errored=(\d+) ready=(\d+)', - status, re.MULTILINE) + m = re.search(r'ManagedCertificatesTotal: (\d+)', status, re.MULTILINE) + assert m, status + assert int(m.group(1)) == 1 + m = re.search(r'ManagedCertificatesOK: (\d+)', status, re.MULTILINE) + assert int(m.group(1)) == 0 + m = re.search(r'ManagedCertificatesRenew: (\d+)', status, re.MULTILINE) + assert int(m.group(1)) == 1 + m = re.search(r'ManagedCertificatesErrored: (\d+)', status, re.MULTILINE) + assert int(m.group(1)) == 0 + m = re.search(r'ManagedCertificatesReady: (\d+)', status, re.MULTILINE) assert int(m.group(1)) == 1 - assert int(m.group(2)) == 0 - assert int(m.group(3)) == 1 - assert int(m.group(4)) == 0 - assert int(m.group(5)) == 1 def test_md_920_011(self, env): # MD with static cert files in base server, see issue #161 |