Add skiplist option to steer clear of skiplisted hosts
authorjob <job@openbsd.org>
Mon, 27 Jun 2022 10:18:27 +0000 (10:18 +0000)
committerjob <job@openbsd.org>
Mon, 27 Jun 2022 10:18:27 +0000 (10:18 +0000)
Blocking outbound connections towards RPKI publication servers based
on IP or IPv6 address in external instrumentation like HTTP proxies
or pf(4) rules is somewhat unwieldy. It might be easier for operators
if we offer a mechanism that cuts at the CA cert SIA parsing step.

OK claudio@ tb@

usr.sbin/rpki-client/extern.h
usr.sbin/rpki-client/main.c
usr.sbin/rpki-client/rpki-client.8

index 81e76f8..fc79967 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: extern.h,v 1.142 2022/06/10 10:36:43 tb Exp $ */
+/*     $OpenBSD: extern.h,v 1.143 2022/06/27 10:18:27 job Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
 #include <openssl/x509.h>
 #include <openssl/x509v3.h>
 
+/*
+ * Distrusted hosts (loaded from skipfile).
+ */
+struct skiplistentry {
+       LIST_ENTRY(skiplistentry)        entry;
+       char                            *value; /* FQDN */
+};
+LIST_HEAD(skiplist, skiplistentry);
+
 /*
  * Enumeration for ASN.1 explicit tags in RSC eContent
  */
@@ -430,6 +439,7 @@ struct stats {
        size_t   extra_files; /* number of superfluous files */
        size_t   del_dirs; /* number of directories removed in cleanup */
        size_t   brks; /* number of BGPsec Router Key (BRK) certificates */
+       size_t   skiplistentries; /* number of skiplist entries */
        struct timeval  elapsed_time;
        struct timeval  user_time;
        struct timeval  system_time;
@@ -689,6 +699,8 @@ int mkpathat(int, const char *);
 #define RPKI_PATH_OUT_DIR      "/var/db/rpki-client"
 #define RPKI_PATH_BASE_DIR     "/var/cache/rpki-client"
 
+#define DEFAULT_SKIPLIST_FILE  "/etc/rpki/skiplist"
+
 /* Maximum number of TAL files we'll load. */
 #define        TALSZ_MAX       8
 
index 9f4ae29..4949d75 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: main.c,v 1.207 2022/06/25 20:25:43 tb Exp $ */
+/*     $OpenBSD: main.c,v 1.208 2022/06/27 10:18:27 job Exp $ */
 /*
  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -25,6 +25,7 @@
 #include <sys/wait.h>
 
 #include <assert.h>
+#include <ctype.h>
 #include <err.h>
 #include <errno.h>
 #include <dirent.h>
@@ -66,6 +67,8 @@ int   filemode;
 int    rrdpon = 1;
 int    repo_timeout;
 
+struct skiplist skiplist = LIST_HEAD_INITIALIZER(skiplist);
+
 struct stats    stats;
 
 /*
@@ -415,10 +418,22 @@ queue_add_from_tal(struct tal *tal)
 static void
 queue_add_from_cert(const struct cert *cert)
 {
-       struct repo     *repo;
-       char            *nfile, *npath;
-       const char      *uri, *repouri, *file;
-       size_t           repourisz;
+       struct repo             *repo;
+       struct skiplistentry    *sle;
+       char                    *nfile, *npath, *host;
+       const char              *uri, *repouri, *file;
+       size_t                   repourisz;
+
+       LIST_FOREACH(sle, &skiplist, entry) {
+               if (strncmp(cert->repo, "rsync://", 8) != 0)
+                       errx(1, "unexpected protocol");
+               host = cert->repo + 8;
+
+               if (strncasecmp(host, sle->value, strcspn(host, "/")) == 0) {
+                       warnx("skipping %s (listed in skiplist)", cert->repo);
+                       return;
+               }
+       }
 
        repo = repo_lookup(cert->talid, cert->repo,
            rrdpon ? cert->notify : NULL);
@@ -649,6 +664,57 @@ tal_load_default(void)
        return s;
 }
 
+/*
+ * Load the list of FQDNs from the skiplist which are to be distrusted.
+ * Return 0 on success.
+ */
+static void
+load_skiplist(const char *slf)
+{
+       struct skiplistentry    *sle;
+       FILE                    *fp;
+       char                    *line = NULL;
+       size_t                   linesize = 0, s;
+       ssize_t                  linelen;
+
+       if ((fp = fopen(slf, "r")) == NULL) {
+               if (strcmp(slf, DEFAULT_SKIPLIST_FILE) != 0)
+                       errx(1, "failed to open skiplist %s", slf);
+               return;
+       }
+
+       while ((linelen = getline(&line, &linesize, fp)) != -1) {
+               /* just eat comment lines or empty lines*/
+               if (line[0] == '#' || line[0] == '\n')
+                       continue;
+
+               if (line[0] == ' ' || line[0] == '\t')
+                       errx(1, "invalid entry in skiplist: %s", line);
+
+               /*
+                * Ignore anything after comment sign, whitespaces,
+                * also chop off LF or CR.
+                */
+               line[strcspn(line, " #\r\n\t")] = 0;
+
+               for (s = 0; s < strlen(line); s++)
+                       if (!isalnum((unsigned char)line[s]) &&
+                           !ispunct((unsigned char)line[s]))
+                               errx(1, "invalid entry in skiplist: %s", line);
+
+               if ((sle = malloc(sizeof(struct skiplistentry))) == NULL)
+                       err(1, NULL);
+               if ((sle->value = strdup(line)) == NULL)
+                       err(1, NULL);
+
+               LIST_INSERT_HEAD(&skiplist, sle, entry);
+               stats.skiplistentries++;
+       }
+
+       fclose(fp);
+       free(line);
+}
+
 static void
 check_fs_size(int fd, const char *cachedir)
 {
@@ -724,6 +790,7 @@ main(int argc, char *argv[])
        char            *bind_addr = NULL;
        const char      *cachedir = NULL, *outputdir = NULL;
        const char      *errs, *name;
+       const char      *skiplistfile = NULL;
        struct vrp_tree  vrps = RB_INITIALIZER(&vrps);
        struct brk_tree  brks = RB_INITIALIZER(&brks);
        struct rusage    ru;
@@ -746,12 +813,13 @@ main(int argc, char *argv[])
        cachedir = RPKI_PATH_BASE_DIR;
        outputdir = RPKI_PATH_OUT_DIR;
        repo_timeout = timeout / 4;
+       skiplistfile = DEFAULT_SKIPLIST_FILE;
 
        if (pledge("stdio rpath wpath cpath inet fattr dns sendfd recvfd "
            "proc exec unveil", NULL) == -1)
                err(1, "pledge");
 
-       while ((c = getopt(argc, argv, "b:Bcd:e:fjnorRs:t:T:vV")) != -1)
+       while ((c = getopt(argc, argv, "b:Bcd:e:fjnorRs:S:t:T:vV")) != -1)
                switch (c) {
                case 'b':
                        bind_addr = optarg;
@@ -796,6 +864,9 @@ main(int argc, char *argv[])
                        else
                                repo_timeout = timeout / 4;
                        break;
+               case 'S':
+                       skiplistfile = optarg;
+                       break;
                case 't':
                        if (talsz >= TALSZ_MAX)
                                err(1, "too many tal files specified");
@@ -963,6 +1034,8 @@ main(int argc, char *argv[])
        pfd[3].fd = rrdp;
        queues[3] = &rrdpq;
 
+       load_skiplist(skiplistfile);
+
        /*
         * Prime the process with our TAL files.
         * These will (hopefully) contain links to manifests and we
@@ -1168,6 +1241,7 @@ main(int argc, char *argv[])
            (long long)stats.elapsed_time.tv_sec,
            (long long)stats.user_time.tv_sec,
            (long long)stats.system_time.tv_sec);
+       printf("Skiplist entries: %zu\n", stats.skiplistentries);
        printf("Route Origin Authorizations: %zu (%zu failed parse, %zu invalid)\n",
            stats.roas, stats.roas_fail, stats.roas_invalid);
        printf("BGPsec Router Certificates: %zu\n", stats.brks);
@@ -1193,7 +1267,7 @@ usage:
        fprintf(stderr,
            "usage: rpki-client [-BcjnoRrVv] [-b sourceaddr] [-d cachedir]"
            " [-e rsync_prog]\n"
-           "                   [-s timeout] [-T table] [-t tal]"
+           "                   [-S skiplist] [-s timeout] [-T table] [-t tal]"
            " [outputdir]\n"
            "       rpki-client [-Vv] [-d cachedir] [-t tal] -f file ...\n");
        return 1;
index 4f738fb..d0c63f7 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: rpki-client.8,v 1.65 2022/05/31 18:42:26 tb Exp $
+.\"    $OpenBSD: rpki-client.8,v 1.66 2022/06/27 10:18:27 job Exp $
 .\"
 .\" Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
 .\"
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: May 31 2022 $
+.Dd $Mdocdate: June 27 2022 $
 .Dt RPKI-CLIENT 8
 .Os
 .Sh NAME
@@ -26,6 +26,7 @@
 .Op Fl b Ar sourceaddr
 .Op Fl d Ar cachedir
 .Op Fl e Ar rsync_prog
+.Op Fl S Ar skiplist
 .Op Fl s Ar timeout
 .Op Fl T Ar table
 .Op Fl t Ar tal
@@ -151,6 +152,23 @@ If RRDP fails, RSYNC will be used.
 This is the default.
 Mutually exclusive with
 .Fl n .
+.It Fl S Ar skiplist
+Do not connect to any hosts listed in the
+.Ar skiplist 
+file.
+Entries in the
+.Ar skiplist
+are newline separated
+.Em Fully Qualified Domain Names Pq FQDNs .
+A
+.Ql #
+indicates the beginning of a comment; characters up to the end of the line are
+not interpreted.
+The skip filter is enforced during processing of the
+.Em Subject Information Access Pq SIA
+extension in CA certificates, thus applies to both RSYNC and RRDP connections.
+By default load entries from
+.Pa /etc/rpki/skiplist .
 .It Fl s Ar timeout
 Terminate after
 .Ar timeout
@@ -215,6 +233,10 @@ URL of HTTP proxy to use.
 default TAL files used unless
 .Fl t Ar tal
 is specified.
+.It Pa /etc/rpki/skiplist
+default skiplist file, unless
+.Fl S Ar skiplist
+is specified.
 .It Pa /var/cache/rpki-client
 cached repository data.
 .It Pa /var/db/rpki-client/openbgpd