Index | Thread | Search

From:
Job Snijders <job@openbsd.org>
Subject:
rpki-client: expose Manifest sequence number gaps in log & telemetry
To:
tech@openbsd.org
Date:
Sat, 2 Nov 2024 11:56:00 +0000

Download raw body.

Thread
Alloah,

I think it is helpful for network operators, publication point
operators, and CA operators to have more insight into whether the RP
noticed an issuance gap between two versions of a given manifest.

Detection of Manifest issuance gaps can be useful in a number of ways:

* high number of gaps all the time might be an indication the RP is not
  refreshing often enough
* the RFC 8181 publication server's ingress API endpoint has issues
* the RFC 8181 publication client has trouble reaching the server
* the CA is trying to issue manifests more than once a second
* the CA's private keys (RPKI + BPKI) are in use on a (cloned) system
* the CA's issuance database is broken

Correlation opportunities
-------------------------

Detection of a gap means some of the CA's intermediate states were
occluded from the RP; the RP operator might want to correlate this to
traffic shifts in BGP, and repository reachability issues.

The below patch emits a warning per manifest, adds metrics to the
openmetrics output, and displays a summary at the end of the run.

With tb@

OK?

Kind regards,

Job

Index: extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
diff -u -p -r1.228 extern.h
--- extern.h	12 Sep 2024 10:33:25 -0000	1.228
+++ extern.h	2 Nov 2024 11:47:19 -0000
@@ -219,6 +219,7 @@ struct mft {
 	unsigned int	 repoid;
 	int		 talid;
 	int		 certid;
+	int		 seqnum_gap; /* was there a gap compared to prev mft? */
 };
 
 /*
@@ -584,6 +585,7 @@ enum stype {
 	STYPE_DEC_UNIQUE,
 	STYPE_PROVIDERS,
 	STYPE_OVERFLOW,
+	STYPE_SEQNUM_GAP,
 };
 
 struct repo;
@@ -598,6 +600,7 @@ struct repotalstats {
 	uint32_t	 certs; /* certificates */
 	uint32_t	 certs_fail; /* invalid certificate */
 	uint32_t	 mfts; /* total number of manifests */
+	uint32_t	 mfts_gap; /* manifests with sequence gaps */
 	uint32_t	 mfts_fail; /* failing syntactic parse */
 	uint32_t	 roas; /* route origin authorizations */
 	uint32_t	 roas_fail; /* failing syntactic parse */
@@ -690,6 +693,7 @@ struct mft	*mft_parse(X509 **, const cha
 struct mft	*mft_read(struct ibuf *);
 int		 mft_compare_issued(const struct mft *, const struct mft *);
 int		 mft_compare_seqnum(const struct mft *, const struct mft *);
+int		 mft_seqnum_gap_present(const struct mft *, const struct mft *);
 
 void		 roa_buffer(struct ibuf *, const struct roa *);
 void		 roa_free(struct roa *);
Index: filemode.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/filemode.c,v
diff -u -p -r1.49 filemode.c
--- filemode.c	20 Aug 2024 13:31:49 -0000	1.49
+++ filemode.c	2 Nov 2024 11:47:19 -0000
@@ -41,6 +41,8 @@
 #include "extern.h"
 #include "json.h"
 
+extern BN_CTX		*bn_ctx;
+
 static X509_STORE_CTX	*ctx;
 static struct auth_tree	 auths = RB_INITIALIZER(&auths);
 static struct crl_tree	 crlt = RB_INITIALIZER(&crlt);
@@ -722,6 +724,9 @@ proc_filemode(int fd)
 
 	if ((ctx = X509_STORE_CTX_new()) == NULL)
 		err(1, "X509_STORE_CTX_new");
+	if ((bn_ctx = BN_CTX_new()) == NULL)
+		err(1, "BN_CTX_new");
+
 	TAILQ_INIT(&q);
 
 	msgbuf_init(&msgq);
@@ -781,6 +786,8 @@ proc_filemode(int fd)
 	crl_tree_free(&crlt);
 
 	X509_STORE_CTX_free(ctx);
+	BN_CTX_free(bn_ctx);
+
 	ibuf_free(inbuf);
 
 	exit(0);
Index: main.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v
diff -u -p -r1.268 main.c
--- main.c	23 Oct 2024 12:09:14 -0000	1.268
+++ main.c	2 Nov 2024 11:47:19 -0000
@@ -639,6 +639,8 @@ entity_process(struct ibuf *b, struct st
 			break;
 		}
 		mft = mft_read(b);
+		if (mft->seqnum_gap)
+			repo_stat_inc(rp, talid, type, STYPE_SEQNUM_GAP);
 		queue_add_from_mft(mft);
 		mft_free(mft);
 		break;
@@ -764,6 +766,7 @@ sum_stats(const struct repo *rp, const s
 
 	out->mfts += in->mfts;
 	out->mfts_fail += in->mfts_fail;
+	out->mfts_gap += in->mfts_gap;
 	out->certs += in->certs;
 	out->certs_fail += in->certs_fail;
 	out->roas += in->roas;
@@ -1500,8 +1503,9 @@ main(int argc, char *argv[])
 	    stats.repo_tal_stats.certs, stats.repo_tal_stats.certs_fail);
 	printf("Trust Anchor Locators: %u (%u invalid)\n",
 	    stats.tals, talsz - stats.tals);
-	printf("Manifests: %u (%u failed parse)\n",
-	    stats.repo_tal_stats.mfts, stats.repo_tal_stats.mfts_fail);
+	printf("Manifests: %u (%u failed parse, %u seqnum gaps)\n",
+	    stats.repo_tal_stats.mfts, stats.repo_tal_stats.mfts_fail,
+	    stats.repo_tal_stats.mfts_gap);
 	printf("Certificate revocation lists: %u\n", stats.repo_tal_stats.crls);
 	printf("Ghostbuster records: %u\n", stats.repo_tal_stats.gbrs);
 	printf("Trust Anchor Keys: %u\n", stats.repo_tal_stats.taks);
Index: mft.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/mft.c,v
diff -u -p -r1.119 mft.c
--- mft.c	12 Sep 2024 10:33:25 -0000	1.119
+++ mft.c	2 Nov 2024 11:47:19 -0000
@@ -35,6 +35,7 @@
 #include "extern.h"
 
 extern ASN1_OBJECT	*mft_oid;
+BN_CTX			*bn_ctx;
 
 /*
  * Types and templates for the Manifest eContent, RFC 6486, section 4.2.
@@ -538,6 +539,7 @@ mft_buffer(struct ibuf *b, const struct 
 	io_simple_buffer(b, &p->repoid, sizeof(p->repoid));
 	io_simple_buffer(b, &p->talid, sizeof(p->talid));
 	io_simple_buffer(b, &p->certid, sizeof(p->certid));
+	io_simple_buffer(b, &p->seqnum_gap, sizeof(p->seqnum_gap));
 	io_str_buffer(b, p->path);
 
 	io_str_buffer(b, p->aia);
@@ -571,6 +573,7 @@ mft_read(struct ibuf *b)
 	io_read_buf(b, &p->repoid, sizeof(p->repoid));
 	io_read_buf(b, &p->talid, sizeof(p->talid));
 	io_read_buf(b, &p->certid, sizeof(p->certid));
+	io_read_buf(b, &p->seqnum_gap, sizeof(p->seqnum_gap));
 	io_read_str(b, &p->path);
 
 	io_read_str(b, &p->aia);
@@ -627,4 +630,36 @@ mft_compare_seqnum(const struct mft *a, 
 		return -1;
 
 	return 0;
+}
+
+/*
+ * Test if there is a gap in the sequence numbers of two MFTs.
+ * Return 1 if a gap is detected.
+ */
+int
+mft_seqnum_gap_present(const struct mft *a, const struct mft *b)
+{
+	BIGNUM *diff, *seqnum_a, *seqnum_b;
+	int ret = 0;
+
+	BN_CTX_start(bn_ctx);
+	if ((diff = BN_CTX_get(bn_ctx)) == NULL ||
+	    (seqnum_a = BN_CTX_get(bn_ctx)) == NULL ||
+	    (seqnum_b = BN_CTX_get(bn_ctx)) == NULL)
+		errx(1, "BN_CTX_get");
+
+	if (!BN_hex2bn(&seqnum_a, a->seqnum))
+		errx(1, "BN_hex2bn");
+
+	if (!BN_hex2bn(&seqnum_b, b->seqnum))
+		errx(1, "BN_hex2bn");
+
+	if (!BN_sub(diff, seqnum_a, seqnum_b))
+		errx(1, "BN_sub");
+
+	ret = !BN_is_one(diff);
+
+	BN_CTX_end(bn_ctx);
+
+	return ret;
 }
Index: output-ometric.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/output-ometric.c,v
diff -u -p -r1.11 output-ometric.c
--- output-ometric.c	3 Sep 2024 15:04:48 -0000	1.11
+++ output-ometric.c	2 Nov 2024 11:47:19 -0000
@@ -47,6 +47,8 @@ set_common_stats(const struct repotalsta
 	    OKV("type", "state"), OKV("manifest", "valid"), ol);
 	ometric_set_int_with_labels(metric, in->mfts_fail,
 	    OKV("type", "state"), OKV("manifest", "failed parse"), ol);
+	ometric_set_int_with_labels(metric, in->mfts_gap,
+	    OKV("type", "state"), OKV("manifest", "sequence gap"), ol);
 
 	ometric_set_int_with_labels(metric, in->roas,
 	    OKV("type", "state"), OKV("roa", "valid"), ol);
Index: parser.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/parser.c,v
diff -u -p -r1.143 parser.c
--- parser.c	29 Aug 2024 13:46:28 -0000	1.143
+++ parser.c	2 Nov 2024 11:47:19 -0000
@@ -40,6 +40,8 @@
 
 extern int certid;
 
+extern BN_CTX		*bn_ctx;
+
 static X509_STORE_CTX	*ctx;
 static struct auth_tree	 auths = RB_INITIALIZER(&auths);
 static struct crl_tree	 crlt = RB_INITIALIZER(&crlt);
@@ -453,6 +455,14 @@ proc_parser_mft_pre(struct entity *entp,
 		goto err;
 	}
 
+	if (seqnum_cmp > 0) {
+		if (mft_seqnum_gap_present(mft, cached_mft)) {
+			mft->seqnum_gap = 1;
+			warnx("%s: seqnum gap detected #%s -> #%s", file,
+			    cached_mft->seqnum, mft->seqnum);
+		}
+	}
+
 	return mft;
 
  err:
@@ -1055,6 +1065,8 @@ proc_parser(int fd)
 
 	if ((ctx = X509_STORE_CTX_new()) == NULL)
 		err(1, "X509_STORE_CTX_new");
+	if ((bn_ctx = BN_CTX_new()) == NULL)
+		err(1, "BN_CTX_new");
 
 	TAILQ_INIT(&q);
 
@@ -1114,6 +1126,8 @@ proc_parser(int fd)
 	crl_tree_free(&crlt);
 
 	X509_STORE_CTX_free(ctx);
+	BN_CTX_free(bn_ctx);
+
 	msgbuf_clear(&msgq);
 
 	ibuf_free(inbuf);
Index: repo.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/repo.c,v
diff -u -p -r1.68 repo.c
--- repo.c	27 Sep 2024 12:55:03 -0000	1.68
+++ repo.c	2 Nov 2024 11:47:20 -0000
@@ -1500,6 +1500,8 @@ repo_stat_inc(struct repo *rp, int talid
 			rp->stats[talid].mfts++;
 		if (subtype == STYPE_FAIL)
 			rp->stats[talid].mfts_fail++;
+		if (subtype == STYPE_SEQNUM_GAP)
+			rp->stats[talid].mfts_gap++;
 		break;
 	case RTYPE_ROA:
 		switch (subtype) {