Download raw body.
rpki-client: collect non-functional CAs
A non-functional CA is a CA which has not signed any currently valid
Manifest. Such a CA does not meaningfully participate in the RPKI and
only consumes resources. The diff below collects these CAs and adds
support code for outputting some info on them into the JSON dump. This
is motivated by Job's policy proposal on revoking persistently
non-functional CAs on the the RIPE-NCC routing-wg list:
https://mailman.ripe.net/archives/list/routing-wg@ripe.net/thread/USQUMNOE3L3UUD3JZVI6LH7VMDRPL7K4/
The strategy is straightforward: build a tree of TA/CA certs sorted by
certid and when we encounter a mft issued by the CA with certid remove
it from the tree. This will also make it straightforward to add that to
stats/ometrics.
One slightly tricky bit is to avoid flagging CAs that were skipped or
not shortlisted. That's why the call to cert_insert_nca() is where it
is and not in entity_process() like most other trees.
The other annoying bit is to get the path of the cert without .rsync/
and .rrdp/*/ artifacts prepended to it. While this can be obtained by
chopping up the file in entity_process(), I think it's cleaner to
construct the DIR_VALID path and pass that over the pipe.
The third annoying bit is the number of trees we need to pass to the
output functions. We should probably hang all the trees off a single
struct so we can avoid this churn when we add the next tree.
Index: cert.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/cert.c,v
diff -u -p -r1.155 cert.c
--- cert.c 18 Dec 2024 21:12:26 -0000 1.155
+++ cert.c 11 Mar 2025 18:36:39 -0000
@@ -1218,6 +1218,7 @@ cert_free(struct cert *p)
free(p->crl);
free(p->repo);
+ free(p->path);
free(p->mft);
free(p->notify);
free(p->ips);
@@ -1248,6 +1249,7 @@ cert_buffer(struct ibuf *b, const struct
io_simple_buffer(b, p->ips, p->num_ips * sizeof(p->ips[0]));
io_simple_buffer(b, p->ases, p->num_ases * sizeof(p->ases[0]));
+ io_str_buffer(b, p->path);
io_str_buffer(b, p->mft);
io_str_buffer(b, p->notify);
io_str_buffer(b, p->repo);
@@ -1291,6 +1293,7 @@ cert_read(struct ibuf *b)
io_read_buf(b, p->ases, p->num_ases * sizeof(p->ases[0]));
}
+ io_read_str(b, &p->path);
io_read_str(b, &p->mft);
io_read_str(b, &p->notify);
io_read_str(b, &p->repo);
@@ -1456,3 +1459,55 @@ brkcmp(struct brk *a, struct brk *b)
}
RB_GENERATE(brk_tree, brk, entry, brkcmp);
+
+/*
+ * Add each CA cert into the non-functional CA tree.
+ */
+void
+cert_insert_nca(struct nca_tree *tree, const struct cert *cert)
+{
+ struct nonfunc_ca *nca;
+
+ if ((nca = calloc(1, sizeof(*nca))) == NULL)
+ err(1, NULL);
+ if ((nca->location = strdup(cert->path)) == NULL)
+ err(1, NULL);
+ if ((nca->carepo = strdup(cert->repo)) == NULL)
+ err(1, NULL);
+ if ((nca->mfturi = strdup(cert->mft)) == NULL)
+ err(1, NULL);
+ if ((nca->ski = strdup(cert->ski)) == NULL)
+ err(1, NULL);
+ nca->certid = cert->certid;
+ nca->talid = cert->talid;
+
+ if (RB_INSERT(nca_tree, tree, nca) != NULL)
+ errx(1, "non-functional CA tree corrupted");
+}
+
+void
+cert_remove_nca(struct nca_tree *tree, int cid)
+{
+ struct nonfunc_ca *found, needle = { .certid = cid };
+
+ if ((found = RB_FIND(nca_tree, tree, &needle)) != NULL) {
+ RB_REMOVE(nca_tree, tree, found);
+ free(found->location);
+ free(found->carepo);
+ free(found->mfturi);
+ free(found->ski);
+ free(found);
+ }
+}
+
+static inline int
+ncacmp(const struct nonfunc_ca *a, const struct nonfunc_ca *b)
+{
+ if (a->certid < b->certid)
+ return -1;
+ if (a->certid > b->certid)
+ return 1;
+ return 0;
+}
+
+RB_GENERATE(nca_tree, nonfunc_ca, entry, ncacmp);
Index: extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
diff -u -p -r1.237 extern.h
--- extern.h 25 Feb 2025 15:55:26 -0000 1.237
+++ extern.h 11 Mar 2025 18:36:39 -0000
@@ -129,6 +129,7 @@ struct cert {
int talid; /* cert is covered by which TAL */
int certid;
unsigned int repoid; /* repository of this cert file */
+ char *path; /* filename without .rrdp and .rsync prefix */
char *repo; /* CA repository (rsync:// uri) */
char *mft; /* manifest (rsync:// uri) */
char *notify; /* RRDP notify (https:// uri) */
@@ -145,6 +146,27 @@ struct cert {
};
/*
+ * Non-functional CA tree element.
+ * Initially all CA and TA certs are added to this tree.
+ * They are removed once they are the issuer of a valid mft.
+ */
+struct nonfunc_ca {
+ RB_ENTRY(nonfunc_ca) entry;
+ char *location;
+ char *carepo;
+ char *mfturi;
+ char *ski;
+ int certid;
+ int talid;
+};
+
+/*
+ * Tree of nonfunc CAs, sorted by certid.
+ */
+RB_HEAD(nca_tree, nonfunc_ca);
+RB_PROTOTYPE(nca_tree, nonfunc_ca, entry, ncacmp);
+
+/*
* The TAL file conforms to RFC 7730.
* It is the top-level structure of RPKI and defines where we can find
* certificates for TAs (trust anchors).
@@ -687,6 +709,8 @@ struct cert *ta_parse(const char *, stru
size_t);
struct cert *cert_read(struct ibuf *);
void cert_insert_brks(struct brk_tree *, struct cert *);
+void cert_insert_nca(struct nca_tree *, const struct cert *);
+void cert_remove_nca(struct nca_tree *, int);
enum rtype rtype_from_file_extension(const char *);
void mft_buffer(struct ibuf *, const struct mft *);
@@ -966,18 +990,24 @@ extern int outformats;
#define FORMAT_OMETRIC 0x10
int outputfiles(struct vrp_tree *v, struct brk_tree *b,
- struct vap_tree *, struct vsp_tree *, struct stats *);
+ struct vap_tree *, struct vsp_tree *, struct nca_tree *,
+ struct stats *);
int outputheader(FILE *, struct stats *);
int output_bgpd(FILE *, struct vrp_tree *, struct brk_tree *,
- struct vap_tree *, struct vsp_tree *, struct stats *);
+ struct vap_tree *, struct vsp_tree *, struct nca_tree *,
+ struct stats *);
int output_bird(FILE *, struct vrp_tree *, struct brk_tree *,
- struct vap_tree *, struct vsp_tree *, struct stats *);
+ struct vap_tree *, struct vsp_tree *, struct nca_tree *,
+ struct stats *);
int output_csv(FILE *, struct vrp_tree *, struct brk_tree *,
- struct vap_tree *, struct vsp_tree *, struct stats *);
+ struct vap_tree *, struct vsp_tree *, struct nca_tree *,
+ struct stats *);
int output_json(FILE *, struct vrp_tree *, struct brk_tree *,
- struct vap_tree *, struct vsp_tree *, struct stats *);
+ struct vap_tree *, struct vsp_tree *, struct nca_tree *,
+ struct stats *);
int output_ometric(FILE *, struct vrp_tree *, struct brk_tree *,
- struct vap_tree *, struct vsp_tree *, struct stats *);
+ struct vap_tree *, struct vsp_tree *, struct nca_tree *,
+ struct stats *);
void logx(const char *fmt, ...)
__attribute__((format(printf, 1, 2)));
Index: main.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v
diff -u -p -r1.279 main.c
--- main.c 27 Feb 2025 14:23:02 -0000 1.279
+++ main.c 11 Mar 2025 18:36:39 -0000
@@ -488,7 +488,7 @@ queue_add_from_tal(struct tal *tal)
* Add a manifest (MFT) found in an X509 certificate, RFC 6487.
*/
static void
-queue_add_from_cert(const struct cert *cert)
+queue_add_from_cert(const struct cert *cert, struct nca_tree *ncas)
{
struct repo *repo;
struct fqdnlistentry *le;
@@ -549,6 +549,7 @@ queue_add_from_cert(const struct cert *c
err(1, NULL);
}
+ cert_insert_nca(ncas, cert);
entityq_add(npath, nfile, RTYPE_MFT, DIR_UNKNOWN, repo, NULL, 0,
cert->talid, cert->certid, NULL);
}
@@ -562,7 +563,7 @@ queue_add_from_cert(const struct cert *c
static void
entity_process(struct ibuf *b, struct stats *st, struct vrp_tree *tree,
struct brk_tree *brktree, struct vap_tree *vaptree,
- struct vsp_tree *vsptree)
+ struct vsp_tree *vsptree, struct nca_tree *ncatree)
{
enum rtype type;
struct tal *tal;
@@ -619,7 +620,7 @@ entity_process(struct ibuf *b, struct st
switch (cert->purpose) {
case CERT_PURPOSE_TA:
case CERT_PURPOSE_CA:
- queue_add_from_cert(cert);
+ queue_add_from_cert(cert, ncatree);
break;
case CERT_PURPOSE_BGPSEC_ROUTER:
cert_insert_brks(brktree, cert);
@@ -641,6 +642,7 @@ entity_process(struct ibuf *b, struct st
if (mft->seqnum_gap)
repo_stat_inc(rp, talid, type, STYPE_SEQNUM_GAP);
queue_add_from_mft(mft);
+ cert_remove_nca(ncatree, mft->certid);
mft_free(mft);
break;
case RTYPE_CRL:
@@ -987,6 +989,7 @@ main(int argc, char *argv[])
struct vsp_tree vsps = RB_INITIALIZER(&vsps);
struct brk_tree brks = RB_INITIALIZER(&brks);
struct vap_tree vaps = RB_INITIALIZER(&vaps);
+ struct nca_tree ncas = RB_INITIALIZER(&ncas);
struct rusage ru;
struct timespec start_time, now_time;
@@ -1403,7 +1406,7 @@ main(int argc, char *argv[])
}
while ((b = io_buf_get(queues[0])) != NULL) {
entity_process(b, &stats, &vrps, &brks, &vaps,
- &vsps);
+ &vsps, &ncas);
ibuf_free(b);
}
}
@@ -1496,7 +1499,7 @@ main(int argc, char *argv[])
}
repo_stats_collect(sum_repostats, &stats.repo_stats);
- if (outputfiles(&vrps, &brks, &vaps, &vsps, &stats))
+ if (outputfiles(&vrps, &brks, &vaps, &vsps, &ncas, &stats))
rc = 1;
printf("Processing time %lld seconds "
Index: output-bgpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-bgpd.c,v
diff -u -p -r1.32 output-bgpd.c
--- output-bgpd.c 13 Nov 2024 12:51:04 -0000 1.32
+++ output-bgpd.c 11 Mar 2025 18:36:39 -0000
@@ -21,7 +21,8 @@
int
output_bgpd(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
- struct vap_tree *vaps, struct vsp_tree *vsps, struct stats *st)
+ struct vap_tree *vaps, struct vsp_tree *vsps, struct nca_tree *ncas,
+ struct stats *st)
{
struct vrp *vrp;
struct vap *vap;
Index: output-bird.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-bird.c,v
diff -u -p -r1.22 output-bird.c
--- output-bird.c 3 Jan 2025 10:32:21 -0000 1.22
+++ output-bird.c 11 Mar 2025 18:36:39 -0000
@@ -22,7 +22,8 @@
int
output_bird(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
- struct vap_tree *vaps, struct vsp_tree *vsps, struct stats *st)
+ struct vap_tree *vaps, struct vsp_tree *vsps, struct nca_tree *ncas,
+ struct stats *st)
{
struct vrp *v;
struct vap *vap;
Index: output-csv.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-csv.c,v
diff -u -p -r1.14 output-csv.c
--- output-csv.c 22 Feb 2024 12:49:42 -0000 1.14
+++ output-csv.c 11 Mar 2025 18:36:39 -0000
@@ -21,7 +21,8 @@
int
output_csv(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
- struct vap_tree *vaps, struct vsp_tree *vsps, struct stats *st)
+ struct vap_tree *vaps, struct vsp_tree *vsps, struct nca_tree *ncas,
+ struct stats *st)
{
struct vrp *v;
Index: output-json.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-json.c,v
diff -u -p -r1.51 output-json.c
--- output-json.c 13 Nov 2024 12:51:04 -0000 1.51
+++ output-json.c 11 Mar 2025 18:36:39 -0000
@@ -145,11 +145,13 @@ output_spl(struct vsp_tree *vsps)
int
output_json(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
- struct vap_tree *vaps, struct vsp_tree *vsps, struct stats *st)
+ struct vap_tree *vaps, struct vsp_tree *vsps, struct nca_tree *ncas,
+ struct stats *st)
{
- char buf[64];
- struct vrp *v;
- struct brk *b;
+ char buf[64];
+ struct vrp *v;
+ struct brk *b;
+ struct nonfunc_ca *nca;
json_do_start(out);
outputheader_json(st);
@@ -178,6 +180,18 @@ output_json(FILE *out, struct vrp_tree *
json_do_int("expires", b->expires);
json_do_end();
}
+ json_do_end();
+
+ json_do_array("nonfunc_cas");
+ RB_FOREACH(nca, nca_tree, ncas) {
+ json_do_object("nca", 1);
+ json_do_string("location", nca->location);
+ json_do_string("ta", taldescs[nca->talid]);
+ json_do_string("caRepository", nca->carepo);
+ json_do_string("rpkiManifest", nca->mfturi);
+ json_do_string("ski", nca->ski);
+ json_do_end();
+ };
json_do_end();
if (!excludeaspa)
Index: output-ometric.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-ometric.c,v
diff -u -p -r1.12 output-ometric.c
--- output-ometric.c 2 Nov 2024 12:30:28 -0000 1.12
+++ output-ometric.c 11 Mar 2025 18:36:39 -0000
@@ -166,7 +166,8 @@ repo_stats(const struct repo *rp, const
int
output_ometric(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
- struct vap_tree *vaps, struct vsp_tree *vsps, struct stats *st)
+ struct vap_tree *vaps, struct vsp_tree *vsps, struct nca_tree *ncas,
+ struct stats *st)
{
struct olabels *ol;
const char *keys[4] = { "nodename", "domainname", "release", NULL };
Index: output.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output.c,v
diff -u -p -r1.38 output.c
--- output.c 3 Jan 2025 10:14:32 -0000 1.38
+++ output.c 11 Mar 2025 18:36:39 -0000
@@ -64,7 +64,8 @@ static const struct outputs {
int format;
char *name;
int (*fn)(FILE *, struct vrp_tree *, struct brk_tree *,
- struct vap_tree *, struct vsp_tree *, struct stats *);
+ struct vap_tree *, struct vsp_tree *, struct nca_tree *,
+ struct stats *);
} outputs[] = {
{ FORMAT_OPENBGPD, "openbgpd", output_bgpd },
{ FORMAT_BIRD, "bird", output_bird },
@@ -124,7 +125,7 @@ prune_as0_tals(struct vrp_tree *vrps)
int
outputfiles(struct vrp_tree *v, struct brk_tree *b, struct vap_tree *a,
- struct vsp_tree *p, struct stats *st)
+ struct vsp_tree *p, struct nca_tree *ncas, struct stats *st)
{
int i, rc = 0;
@@ -146,7 +147,7 @@ outputfiles(struct vrp_tree *v, struct b
rc = 1;
continue;
}
- if ((*outputs[i].fn)(fout, v, b, a, p, st) != 0) {
+ if ((*outputs[i].fn)(fout, v, b, a, p, ncas, st) != 0) {
warn("output for %s format failed", outputs[i].name);
fclose(fout);
output_cleantmp();
Index: parser.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/parser.c,v
diff -u -p -r1.150 parser.c
--- parser.c 12 Mar 2025 07:42:39 -0000 1.150
+++ parser.c 12 Mar 2025 08:56:51 -0000
@@ -596,6 +596,13 @@ proc_parser_cert(char *file, const unsig
if (cert->purpose == CERT_PURPOSE_CA)
auth_insert(file, &auths, cert, a);
+ cert->path = parse_filepath(entp->repoid, entp->path, entp->file,
+ DIR_VALID);
+ if (cert->path == NULL) {
+ warnx("%s: failed to create file path", file);
+ goto out;
+ }
+
return cert;
out:
@@ -677,6 +684,9 @@ proc_parser_root_cert(struct entity *ent
}
if ((cmp = proc_parser_ta_cmp(cert1, cert2)) > 0) {
+ if ((cert1->path = strdup(file2)) == NULL)
+ err(1, NULL);
+
cert_free(cert2);
free(file2);
@@ -694,6 +704,8 @@ proc_parser_root_cert(struct entity *ent
if (cert2 != NULL) {
cert2->talid = entp->talid;
auth_insert(file2, &auths, cert2, NULL);
+ if ((cert2->path = strdup(file2)) == NULL)
+ err(1, NULL);
}
*out_cert = cert2;
rpki-client: collect non-functional CAs