mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-09-22 01:14:54 +02:00
Generated
+51
-1
@@ -1,5 +1,11 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.10"
|
||||
@@ -206,6 +212,15 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.4.2"
|
||||
@@ -625,6 +640,15 @@ dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "intervaltree"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "566d5aa3b5cc5c5809cc1a9c9588d917a634248bfc58f7ea14e354e71595a32c"
|
||||
dependencies = [
|
||||
"smallvec 1.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
@@ -661,7 +685,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "krill"
|
||||
version = "0.6.3"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"base64 0.10.1",
|
||||
"bcder",
|
||||
@@ -676,7 +700,9 @@ dependencies = [
|
||||
"hex",
|
||||
"hyper",
|
||||
"ignore",
|
||||
"intervaltree",
|
||||
"libc",
|
||||
"libflate",
|
||||
"log 0.4.8",
|
||||
"openssl",
|
||||
"pretty",
|
||||
@@ -706,6 +732,24 @@ version = "0.2.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
|
||||
|
||||
[[package]]
|
||||
name = "libflate"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1fbe6b967a94346446d37ace319ae85be7eca261bb8149325811ac435d35d64"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"crc32fast",
|
||||
"libflate_lz77",
|
||||
"rle-decode-fast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libflate_lz77"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3286f09f7d4926fc486334f28d8d2e6ebe4f7f9994494b6dab27ddfad2c9b11b"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.3.3"
|
||||
@@ -1342,6 +1386,12 @@ dependencies = [
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rle-decode-fast"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac"
|
||||
|
||||
[[package]]
|
||||
name = "rpki"
|
||||
version = "0.9.0"
|
||||
|
||||
+3
-1
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
# Note: some of these values are also used when building Debian packages below.
|
||||
name = "krill"
|
||||
version = "0.6.3"
|
||||
version = "0.7.0"
|
||||
edition = "2018"
|
||||
authors = [ "The NLnet Labs RPKI team <rpki-team@nlnetlabs.nl>" ]
|
||||
description = "Resource Public Key Infrastructure (RPKI) daemon"
|
||||
@@ -24,6 +24,8 @@ futures = "0.3.4"
|
||||
futures-util = "0.3.4"
|
||||
hex = "^0.3"
|
||||
hyper = "^0.13"
|
||||
intervaltree = "0.2.6"
|
||||
libflate = "1.0.0"
|
||||
log = "^0.4"
|
||||
openssl = { version = "^0.10", features = ["v110"] }
|
||||
pretty = "0.5.2" # for testing
|
||||
|
||||
+38
-4
@@ -3,7 +3,41 @@
|
||||
Please see [here](https://github.com/NLnetLabs/krill/projects?query=is%3Aopen+sort%3Aname-asc)
|
||||
for planned releases.
|
||||
|
||||
## 0.6.3 Release 'Play it again, Sam'
|
||||
## 0.7.0 'Escondidinho de Lagosta'
|
||||
|
||||
This release brings significant improvements aimed at maintaining your ROAs. For now, Krill
|
||||
will download aggregated BGP dumps from the RIPE NCC Routing Information Service (*) and
|
||||
analyse how your ROAs affect announcements seen for your resources. In future we will extend
|
||||
this system, so that it can use near-real-time data, or even a local feed with your own BGP
|
||||
information instead.
|
||||
|
||||
For these changes to work well we needed to do some work on cleaning up existing ROAs. Until
|
||||
now Krill has allowed the creation of essentially duplicate, or nonsensical ROAs, such as:
|
||||
* ROAs for an ASN and prefix with and without an explicit max length matching the prefix
|
||||
* ROAs for a prefix and ASN which were already permitted by another ROA.
|
||||
|
||||
On upgrade Krill will clean up such redundant authorizations for ROAs. For example if the
|
||||
following authorizations would exist:
|
||||
|
||||
192.168.0.0/16 => 64496
|
||||
192.168.0.0/24 => 64496
|
||||
192.168.0.0/16-24 => 64496
|
||||
|
||||
Then only this last authorization needs to be kept, the first two are also covered by it.
|
||||
|
||||
Before this release it was also possible to have the same authorization with, and without, using
|
||||
an explicit max length. For example:
|
||||
|
||||
192.168.0.0/16 => 64496
|
||||
192.168.0.0/16-16 => 64496
|
||||
|
||||
Now Krill will *always* use an explicit max length in the definitions. Note however, that it is
|
||||
still best practice to use the same max length as the announced prefix length, so Krill will just
|
||||
set this by default if it is not specified.
|
||||
|
||||
*: https://www.ripe.net/analyse/internet-measurements/routing-information-service-ris
|
||||
|
||||
## 0.6.3 'Play it again, Sam'
|
||||
|
||||
This release addresses an issue where users with a CA that has delegated children, which in turn
|
||||
had performed a key roll over in the past, could not upgrade to Release 0.6.2.
|
||||
@@ -18,7 +52,7 @@ Release which is due in 2-4 weeks:
|
||||
* Inconsistent use of "cas" in `krillc bulk` subcommand summary text (#254)
|
||||
* Be consistent when referring to ending with a / (#255)
|
||||
|
||||
## 0.6.2 Release 'That was even faster!'
|
||||
## 0.6.2 'That was even faster!'
|
||||
|
||||
So, as it turns out.. the code used to determine the age of snapshot files used in the previous
|
||||
release was not safe on all platforms. This release fixes this!
|
||||
@@ -26,7 +60,7 @@ release was not safe on all platforms. This release fixes this!
|
||||
Users who upgraded to 0.6.1 and see messages like: "Creation time is not available on this
|
||||
platform currently" in their logs, please upgrade!
|
||||
|
||||
## 0.6.1 Release 'That was fast!'
|
||||
## 0.6.1 'That was fast!'
|
||||
|
||||
This release fixes an issue where the Krill Repository Server deleted RRDP snapshot files as soon
|
||||
as a new notification file was published. This leads to issues in case a cached notification file
|
||||
@@ -37,7 +71,7 @@ Users who use Krill as their own Repository Server are advised to upgrade.
|
||||
Users who publish at a repository provided to them by a third party (e.g. nic.br) can safely skip
|
||||
this release.
|
||||
|
||||
## 0.6.0 Release 'Go with the Flow'
|
||||
## 0.6.0 'Go with the Flow'
|
||||
|
||||
The most visible change in this release is that the embedded Lagosta UI now includes French, Greek
|
||||
and Spanish translations. But, the vast majority of the work went into making Krill use asynchronous
|
||||
|
||||
+42
-28
@@ -28,6 +28,30 @@
|
||||
#
|
||||
### pid_file = "./data/krill.pid"
|
||||
|
||||
# Specify the base public service URI hostname and port.
|
||||
#
|
||||
# The default service URI is set to https://localhost:3000/ regardless of the
|
||||
# IP and port configured above (but matching their default). This is fine for
|
||||
# simple setups where you use Krill to run your own CA only and you use the
|
||||
# CLI from localhost.
|
||||
#
|
||||
# However, if you need to access Krill remotely, or if you are serving as a
|
||||
# parent CA, or Publication Server, to others, then make sure that you use a
|
||||
# public URI here *and* make sure that you use a proxy server with a proper
|
||||
# HTTPS certificate in front of Krill.
|
||||
#
|
||||
# At present this MUST be an https URI with a hostname and optional port number only.
|
||||
# It is not allowed to use a Krill specific path prefix. If you have a strong
|
||||
# motivation for this, then please commont on the following github issue:
|
||||
# https://github.com/NLnetLabs/krill/issues/263
|
||||
#
|
||||
# Krill UI, API and service URIs will be derived as follows:
|
||||
# <service_uri>api/v1/... (api)
|
||||
# <service_uri>rfc8181 (for remote publishers)
|
||||
# <service_uri>rfc6492 (for remote children)
|
||||
# <service_uri>rrdp/.. (override with rddp_service_uri, see below)
|
||||
# <service_uri>... (various UI resources)
|
||||
### service_uri = "https://localhost:3000/"
|
||||
|
||||
# Specify whether an embedded repository should be started. For many users
|
||||
# it will be better to use a repository server provided by a third party, e.g.
|
||||
@@ -37,44 +61,28 @@
|
||||
# setting is set to 'false' (default) AND there are no current publishers (i.e.
|
||||
# all CAs use an external repository).
|
||||
#
|
||||
# For more information on running Krill as Publication Server see:
|
||||
# https://rpki.readthedocs.io/en/latest/krill/publication-server.html
|
||||
#
|
||||
### repo_enabled = false
|
||||
|
||||
# Specify the base rsync repository for this server. Publishers will get
|
||||
# a base URI that is based on the 'publisher_handle' in the XML file.
|
||||
#
|
||||
# Note, you should set up an rsync daemon to expose $data_dir/rsync to serve
|
||||
# this data. The uri defined here should match the module name in your rsync
|
||||
# configuration.
|
||||
# Note, you need to set this parameter if (and only if) you chose to enable
|
||||
# the repository function above (repo_enabled). If you did, you should set up
|
||||
# an rsync daemon to expose $data_dir/rsync to serve this data. The uri defined
|
||||
# here should match the module name in your rsync configuration.
|
||||
#
|
||||
# Furthemore.. note that the default 'localhost' is only allowed to be used
|
||||
# when the KRILL_TEST ENV variable has been set.
|
||||
#
|
||||
### rsync_base = "rsync://localhost/repo/"
|
||||
|
||||
# Specify the base public URI to this service. Other URIs will be derived
|
||||
# from this:
|
||||
# <BASE_URI>api/v1/... (api)
|
||||
# <BASE_URI>rfc8181 (for remote publishers)
|
||||
# <BASE_URI>rfc6492 (for remote children)
|
||||
# <BASE_URI>rrdp/.. (override with rddp_service_uri)
|
||||
# <BASE_URI>ta/ta.cer (on TAL for embedded TA)
|
||||
#
|
||||
# MUST end with a slash.
|
||||
#
|
||||
# Note that the default service URI is set to https://localhost:3000/ regardless of
|
||||
# the IP and port configured above (but matching their default).
|
||||
#
|
||||
# This service URI is fine for simple setups where you use Krill to run your own CA
|
||||
# only and you use the CLI from localhost.
|
||||
#
|
||||
# However, if you need to access Krill remotely then make sure that you use a public
|
||||
# URI here *and* make sure that you use a proxy server with a proper HTTPS certificate
|
||||
# in front of Krill.
|
||||
#
|
||||
### service_uri = "https://localhost:3000/"
|
||||
|
||||
# Use the following if you want to use another public URI to access the RRDP files,
|
||||
# e.g. because you serve them as raw files from another machine with a web server.
|
||||
# Note, you may need to set this parameter if you chose to enable the repository
|
||||
# function above (repo_enabled). By default Krill will use a public RRDP URI
|
||||
# which is based on the service_uri. Use this directive use a different public
|
||||
# URI to access the RRDP files.
|
||||
#
|
||||
### rrdp_service_uri = "$service_uri/rrdp/"
|
||||
|
||||
@@ -170,4 +178,10 @@
|
||||
#
|
||||
# Defaults to NO logging!
|
||||
#
|
||||
### rfc6492_log_dir = </some/path>
|
||||
### rfc6492_log_dir = </some/path>
|
||||
|
||||
# Enable loading BGP Dumps from RIS for ROA vs BGP analysis.
|
||||
#
|
||||
# bgp_risdump_enable = true
|
||||
# bgp_risdump_v4_uri = http://www.ris.ripe.net/dumps/riswhoisdump.IPv4.gz
|
||||
# bgp_risdump_v6_uri = http://www.ris.ripe.net/dumps/riswhoisdump.IPv6.gz
|
||||
+177
-131
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.2"
|
||||
info:
|
||||
title: Krill RPKI Server API
|
||||
version: 0.6.3
|
||||
version: 0.7.0
|
||||
description: |
|
||||
# Introduction
|
||||
Welcome to the documentation for the Krill server API, a JSON based
|
||||
@@ -248,7 +248,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/rfc8181'
|
||||
$ref: '#/components/schemas/Rfc8181'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RFC8183RepositoryResponseXML'
|
||||
@@ -358,7 +358,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/rfc8183'
|
||||
$ref: '#/components/schemas/Rfc8183'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RFC8183RequestXML'
|
||||
@@ -374,8 +374,7 @@ paths:
|
||||
- "Certificate Authorities"
|
||||
summary: Add a child CA.
|
||||
description: |
|
||||
This adds an embedded (in the same Krill instance) or remote child CA.
|
||||
For a remote child CA the request body must include a JSON
|
||||
This adds a child CA. The request body must include a JSON
|
||||
representation of the [RFC 8183 Child Request XML](https://tools.ietf.org/html/rfc8183#section-5.2.1),
|
||||
though the 'tag' and 'child_handle' attributes are ignored.
|
||||
parameters:
|
||||
@@ -387,15 +386,7 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AddCAChildRequest'
|
||||
examples:
|
||||
embedded:
|
||||
value:
|
||||
handle: 'ca'
|
||||
resources:
|
||||
asn: 'AS1'
|
||||
v4: '10.0.0.0/8'
|
||||
v6: '::'
|
||||
auth: 'embedded'
|
||||
remote:
|
||||
child:
|
||||
value:
|
||||
handle: 'ca'
|
||||
resources:
|
||||
@@ -409,7 +400,7 @@ paths:
|
||||
id_cert: '<base64>'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/Rfc6492OrEmbedded'
|
||||
$ref: '#/components/responses/Rfc6492'
|
||||
'400':
|
||||
description: Bad request parameters.
|
||||
content:
|
||||
@@ -539,7 +530,7 @@ paths:
|
||||
- $ref: '#/components/parameters/child_handle'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/Rfc8183OrEmbedded'
|
||||
$ref: '#/components/responses/Rfc8183O'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
@@ -560,16 +551,7 @@ paths:
|
||||
- "Certificate Authorities"
|
||||
summary: Get the RFC 8183 Parent Response for a child.
|
||||
description: |
|
||||
To get the RFC 8183 Parent Response for a child, regardless of whether
|
||||
the parent knows the ID Cert.
|
||||
|
||||
**Note:** You can use 'update child' to add a new ID cert to a child,
|
||||
however, this is a real corner case that can only happen if you
|
||||
mistakenly added a remote child as embedded, rather than using its
|
||||
RFC 8183 Child Request.
|
||||
|
||||
For "embedded" CAs you will not need this parent response when you add
|
||||
the "embedded" parent.
|
||||
To get the RFC 8183 Parent Response for a child.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/ca_handle'
|
||||
- $ref: '#/components/parameters/child_handle'
|
||||
@@ -580,7 +562,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/rfc6492'
|
||||
$ref: '#/components/schemas/Rfc6492'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RFC8183ParentResponseXML'
|
||||
@@ -627,13 +609,8 @@ paths:
|
||||
- "Certificate Authorities"
|
||||
summary: Add a parent to a CA.
|
||||
description: |
|
||||
Adds an embedded or remote parents. For remote parents the RFC 6492
|
||||
will be used. In principle CAs can also use this to talk to a local
|
||||
parent CA in the same krill server, but this is inefficient. Therefore
|
||||
it is also possible to add an 'embedded' parent in this case.
|
||||
|
||||
Note that you MUST specify a repository for your CA before you are
|
||||
allowed to add a parent to it.
|
||||
Add a parent to the CA. Note that your CA will only request a certificate
|
||||
from any of its parents after a repository has been configured for it.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/ca_handle'
|
||||
requestBody:
|
||||
@@ -673,7 +650,7 @@ paths:
|
||||
- $ref: '#/components/parameters/parent_handle'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/Rfc6492OrEmbedded'
|
||||
$ref: '#/components/responses/Rfc6492'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'400':
|
||||
@@ -699,7 +676,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rfc6492OrEmbedded'
|
||||
$ref: '#/components/schemas/Rfc6492'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/Success'
|
||||
@@ -777,12 +754,6 @@ paths:
|
||||
- Request new certificates with SIA entries pointing to the new
|
||||
locations.
|
||||
- (best effort) Clean up of the old repository.
|
||||
|
||||
The new repository can be embedded, or remote. To use a remote
|
||||
repository, the RFC 8181 Repository Response must be encoded into JSON.
|
||||
|
||||
Note: for most users it's better to use a remote repository, e.g. provided
|
||||
by your RIR or NIR.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/ca_handle'
|
||||
requestBody:
|
||||
@@ -790,7 +761,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rfc8181OrEmbedded'
|
||||
$ref: '#/components/schemas/Rfc8181'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/Success'
|
||||
@@ -1011,6 +982,26 @@ paths:
|
||||
'500':
|
||||
$ref: '#/components/responses/GeneralErrorResponse'
|
||||
|
||||
/cas/{ca_handle}/routes/analysis/full:
|
||||
get:
|
||||
operationId: bgp_analysis_full
|
||||
tags:
|
||||
- "Route Authorizations"
|
||||
summary: Show full analysis of all ROAs and Announcments for CA resources
|
||||
description: |
|
||||
Show all known announcements for the CA's resources and their RPKI validity
|
||||
state compared to the CA's ROAs, as well as all the CA's ROAs and how they
|
||||
affect the validatity state of known announcements.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/ca_handle'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/BgpFull'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'500':
|
||||
$ref: '#/components/responses/GeneralErrorResponse'
|
||||
|
||||
/cas/{ca_handle}/issues:
|
||||
get:
|
||||
operationId: show_ca_issues
|
||||
@@ -1145,9 +1136,6 @@ components:
|
||||
ServiceUri:
|
||||
type: string
|
||||
format: uri
|
||||
embedded:
|
||||
type: string
|
||||
enum: ['embedded']
|
||||
rfcCommon:
|
||||
type: object
|
||||
properties:
|
||||
@@ -1156,7 +1144,7 @@ components:
|
||||
nullable: true
|
||||
id_cert:
|
||||
$ref: '#/components/schemas/IdCert'
|
||||
rfc6492:
|
||||
Rfc6492:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/rfcCommon'
|
||||
- type: object
|
||||
@@ -1174,7 +1162,7 @@ components:
|
||||
properties:
|
||||
publisher_handle:
|
||||
$ref: '#/components/schemas/Handle'
|
||||
rfc8181:
|
||||
Rfc8181:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/rfc8181Base'
|
||||
- type: object
|
||||
@@ -1183,25 +1171,13 @@ components:
|
||||
$ref: '#/components/schemas/ServiceUri'
|
||||
repo_info:
|
||||
$ref: '#/components/schemas/RepoInfo'
|
||||
rfc8183:
|
||||
Rfc8183:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/rfcCommon'
|
||||
- type: object
|
||||
properties:
|
||||
child_handle:
|
||||
$ref: '#/components/schemas/Handle'
|
||||
Rfc6492OrEmbedded:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/embedded'
|
||||
- $ref: '#/components/schemas/rfc6492'
|
||||
Rfc8181OrEmbedded:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/embedded'
|
||||
- $ref: '#/components/schemas/rfc8181'
|
||||
Rfc8183OrEmbedded:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/embedded'
|
||||
- $ref: '#/components/schemas/rfc8183'
|
||||
RFC8183PublisherRequestXML:
|
||||
type: object
|
||||
properties:
|
||||
@@ -1386,7 +1362,7 @@ components:
|
||||
resources:
|
||||
$ref: '#/components/schemas/Resources'
|
||||
auth:
|
||||
$ref: '#/components/schemas/Rfc8183OrEmbedded'
|
||||
$ref: '#/components/schemas/Rfc8183'
|
||||
UpdateCAChildRequest:
|
||||
type: object
|
||||
properties:
|
||||
@@ -1412,7 +1388,7 @@ components:
|
||||
- $ref: '#/components/schemas/Handle'
|
||||
- description: The local handle by which your ca refers to this parent.
|
||||
contact:
|
||||
$ref: '#/components/schemas/Rfc6492OrEmbedded'
|
||||
$ref: '#/components/schemas/Rfc6492'
|
||||
AddPublisherRequest:
|
||||
type: object
|
||||
required:
|
||||
@@ -1694,6 +1670,86 @@ components:
|
||||
type: string
|
||||
example: cause
|
||||
|
||||
BgpFullSchema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- asn
|
||||
- prefix
|
||||
- max_length
|
||||
- state
|
||||
- allowed_by
|
||||
- disallowed_by
|
||||
- authorizes
|
||||
- disallows
|
||||
properties:
|
||||
asn:
|
||||
type: integer
|
||||
prefix:
|
||||
type: string
|
||||
max_length:
|
||||
type: integer
|
||||
nullable: true
|
||||
state:
|
||||
type: string
|
||||
allowed_by:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- asn
|
||||
- prefix
|
||||
- max_length
|
||||
properties:
|
||||
asn:
|
||||
type: integer
|
||||
prefix:
|
||||
type: string
|
||||
max_length:
|
||||
type: integer
|
||||
nullable: true
|
||||
disallowed_by:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- asn
|
||||
- prefix
|
||||
- max_length
|
||||
properties:
|
||||
asn:
|
||||
type: integer
|
||||
prefix:
|
||||
type: string
|
||||
max_length:
|
||||
type: integer
|
||||
nullable: true
|
||||
allows:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- asn
|
||||
- prefix
|
||||
properties:
|
||||
asn:
|
||||
type: integer
|
||||
prefix:
|
||||
type: string
|
||||
disallows:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- asn
|
||||
- prefix
|
||||
properties:
|
||||
asn:
|
||||
type: integer
|
||||
prefix:
|
||||
type: string
|
||||
|
||||
CaRoaUnknownResponse:
|
||||
type: object
|
||||
required:
|
||||
@@ -1727,7 +1783,7 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
example: 24
|
||||
|
||||
|
||||
|
||||
CaRoaDuplicateResponse:
|
||||
type: object
|
||||
@@ -1795,8 +1851,8 @@ components:
|
||||
max_length:
|
||||
type: string
|
||||
nullable: true
|
||||
example: 24
|
||||
|
||||
example: 24
|
||||
|
||||
CaRoaNotEntitledResponse:
|
||||
type: object
|
||||
required:
|
||||
@@ -1851,7 +1907,7 @@ components:
|
||||
ca:
|
||||
type: string
|
||||
example: ca
|
||||
|
||||
|
||||
CaUnknownResponse:
|
||||
type: object
|
||||
required:
|
||||
@@ -1897,10 +1953,13 @@ components:
|
||||
responses:
|
||||
Success:
|
||||
description: Success.
|
||||
|
||||
Forbidden:
|
||||
description: Forbidden (incorrect authentication token).
|
||||
|
||||
NotFound:
|
||||
description: Not Found.
|
||||
|
||||
CertAuthIssues:
|
||||
description: Issues for a CA (e.g. cannot reach parent)
|
||||
content:
|
||||
@@ -1929,6 +1988,7 @@ components:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PubDuplicateResponse'
|
||||
|
||||
PubUnknownResponse:
|
||||
description: Unknown publisher
|
||||
content:
|
||||
@@ -2000,6 +2060,28 @@ components:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CaParentIssueResponse'
|
||||
|
||||
BgpFull:
|
||||
description: |
|
||||
Full ROAs vs known BGP Announcements report for this CA.
|
||||
|
||||
This is an array of both ROAs and Announcements, including all optional
|
||||
information that may be applicable to either. I.e. even though the schema
|
||||
includes max_length this value will not be present for announcements. The
|
||||
arrays for 'allows' and 'disallows' will always be empty for announcements,
|
||||
as will the arrays for 'allowed_by' and 'disallowed_by' for ROAs.
|
||||
|
||||
Confusing as this might be, this allows for parsing all kinds of states
|
||||
in a single table. If you want to look at announcements or ROAs only, please
|
||||
look at the other more specific BGP analysis endpoints for those.
|
||||
|
||||
Possible state values are: roa_seen, roa_stale, announcement_valid,
|
||||
announcement_invalid_length, announcement_invalid_asn, announcement_not_found,
|
||||
roa_no_announcement_info
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BgpFullSchema'
|
||||
|
||||
CaRoaUnknownResponse:
|
||||
description: 'Invalid ROA delta: removing a definition which is unknown'
|
||||
content:
|
||||
@@ -2049,7 +2131,6 @@ components:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralErrorResponse'
|
||||
|
||||
|
||||
GetCA:
|
||||
description: Success.
|
||||
content:
|
||||
@@ -2189,6 +2270,7 @@ components:
|
||||
expires: '2019-11-27T10:56:16.172248Z'
|
||||
children:
|
||||
- 'child'
|
||||
|
||||
ListCAs:
|
||||
description: Success.
|
||||
content:
|
||||
@@ -2205,6 +2287,7 @@ components:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Handle'
|
||||
- description: The RFC-8183 handle that uniquely identifies the CA in this Krill instance.
|
||||
|
||||
GetChildCA:
|
||||
description: Success.
|
||||
content:
|
||||
@@ -2216,6 +2299,7 @@ components:
|
||||
$ref: '#/components/schemas/PemIdCert'
|
||||
entitled_resources:
|
||||
$ref: '#/components/schemas/Resources'
|
||||
|
||||
ListPublishers:
|
||||
description: Success.
|
||||
content:
|
||||
@@ -2247,6 +2331,7 @@ components:
|
||||
links:
|
||||
rel: 'self'
|
||||
link: '/api/v1/publishers/ca'
|
||||
|
||||
GetPublisher:
|
||||
description: Success.
|
||||
content:
|
||||
@@ -2283,12 +2368,14 @@ components:
|
||||
uri: 'rsync://<KRILL_SERVER_FQDN>/repo/child/0/A815..A01C.crl'
|
||||
- base64: 'MIIJ..Cg4='
|
||||
uri: 'rsync://<KRILL_SERVER_FQDN>/repo/child/0/3130..3936.roa'
|
||||
|
||||
AddPublisher:
|
||||
description: Success.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/rfc8181'
|
||||
$ref: '#/components/schemas/Rfc8181'
|
||||
|
||||
StalePublishers:
|
||||
description: Success.
|
||||
content:
|
||||
@@ -2320,6 +2407,7 @@ components:
|
||||
links:
|
||||
rel: 'self'
|
||||
link: '/api/v1/publishers/ca'
|
||||
|
||||
GetRepositoryForCA:
|
||||
description: Success.
|
||||
content:
|
||||
@@ -2328,23 +2416,12 @@ components:
|
||||
type: object
|
||||
properties:
|
||||
contact:
|
||||
oneOf:
|
||||
- type: object
|
||||
properties:
|
||||
embedded:
|
||||
$ref: '#/components/schemas/RepoInfo'
|
||||
- type: object
|
||||
properties:
|
||||
rfc8181:
|
||||
$ref: '#/components/schemas/rfc8181'
|
||||
type: object
|
||||
properties:
|
||||
rfc8181:
|
||||
$ref: '#/components/schemas/Rfc8181'
|
||||
examples:
|
||||
embedded:
|
||||
value:
|
||||
contact:
|
||||
embedded:
|
||||
base_uri: 'rsync://localhost/repo/ca/'
|
||||
rpki_notify: 'https://localhost:3000/rrdp/notification.xml'
|
||||
rfc8181:
|
||||
contact:
|
||||
value:
|
||||
contact:
|
||||
rfc8181:
|
||||
@@ -2354,7 +2431,8 @@ components:
|
||||
service_uri: 'https://localhost:3000/rfc8181/ca'
|
||||
repo_info:
|
||||
base_uri: 'rsync://localhost/repo/ca/'
|
||||
rpki_notify: 'https://localhost:3000/rrdp/notification.xml'
|
||||
rpki_notify: 'https://localhost:3000/rrdp/notificati.xml'
|
||||
|
||||
GetCAHistory:
|
||||
description: Success.
|
||||
content:
|
||||
@@ -2382,49 +2460,17 @@ components:
|
||||
type: integer
|
||||
details:
|
||||
type: object
|
||||
Rfc6492OrEmbedded:
|
||||
description: Success.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rfc6492OrEmbedded'
|
||||
examples:
|
||||
rfc6492:
|
||||
value:
|
||||
rfc6492:
|
||||
tag: null
|
||||
id_cert: '<base64>'
|
||||
parent_handle: 'ta'
|
||||
child_handle: 'ca'
|
||||
service_uri: 'https://localhost:3000/rfc6492/ta'
|
||||
embedded:
|
||||
value:
|
||||
'embedded'
|
||||
Rfc8183OrEmbedded:
|
||||
description: Success.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rfc8183OrEmbedded'
|
||||
examples:
|
||||
rfc6492:
|
||||
value:
|
||||
rfc6492:
|
||||
tag: null
|
||||
id_cert: '<base64>'
|
||||
parent_handle: 'ta'
|
||||
child_handle: 'ca'
|
||||
service_uri: 'https://localhost:3000/rfc6492/ta'
|
||||
embedded:
|
||||
value:
|
||||
'embedded'
|
||||
|
||||
examples:
|
||||
rfc6492:
|
||||
value:
|
||||
rfc6492:
|
||||
tag: null
|
||||
id_cert: '<base64>'
|
||||
parent_handle: 'ta'
|
||||
child_handle: 'ca'
|
||||
service_uri: 'https://localhost:3000/rfc6492/ta'
|
||||
Rfc6492:
|
||||
description: Success.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rfc6492'
|
||||
|
||||
Rfc8183O:
|
||||
description: Success.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rfc8183'
|
||||
|
||||
@@ -36,11 +36,11 @@ log_info() {
|
||||
}
|
||||
|
||||
if [ "$1" == "krill" ]; then
|
||||
# Does the opreator want to use their own API token? If so they must
|
||||
# Does the operator want to use their own API token? If so they must
|
||||
# supply the KRILL_AUTH_TOKEN env var.
|
||||
if [ "${KRILL_AUTH_TOKEN}" == "None" ]; then
|
||||
# Generate a unique hard to guess authorisation token and export it
|
||||
# so that the Krill daemon uses it (unless overriden by the Krill
|
||||
# Generate a unique hard to guess authorization token and export it
|
||||
# so that the Krill daemon uses it (unless overridden by the Krill
|
||||
# daemon config file). Only do this if the operator didn't already
|
||||
# supply a token when launching the Docker container.
|
||||
export KRILL_AUTH_TOKEN=$(uuidgen)
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>Lagosta - the Krill UI</title><style>noscript {
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>Krill - RPKI</title><style>noscript {
|
||||
font-family: "Lucida Sans", "Lucida Sans Regular", "Lucida Grande", "Lucida Sans Unicode",
|
||||
Geneva, Verdana, sans-serif;
|
||||
display: grid;
|
||||
|
||||
+10
-8
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -13,6 +13,7 @@ use crate::commons::api::{
|
||||
AllCertAuthIssues, CaRepoDetails, CertAuthIssues, ChildCaInfo, CurrentRepoState,
|
||||
ParentCaContact, PublisherDetails, PublisherList, Token,
|
||||
};
|
||||
use crate::commons::bgp::BgpAnalysisReport;
|
||||
use crate::commons::remote::rfc8183;
|
||||
use crate::commons::util::httpclient;
|
||||
use crate::constants::KRILL_CLI_API_ENV;
|
||||
@@ -211,6 +212,24 @@ impl KrillClient {
|
||||
Ok(ApiResponse::Empty)
|
||||
}
|
||||
|
||||
CaCommand::BgpAnalysisFull(handle) => {
|
||||
let uri = format!("api/v1/cas/{}/routes/analysis/full", handle);
|
||||
let report = self.get_json(&uri).await?;
|
||||
Ok(ApiResponse::BgpAnalysisFull(report))
|
||||
}
|
||||
|
||||
CaCommand::BgpAnalysisAnnouncements(handle) => {
|
||||
let uri = format!("api/v1/cas/{}/routes/analysis/full", handle);
|
||||
let report: BgpAnalysisReport = self.get_json(&uri).await?;
|
||||
Ok(ApiResponse::BgpAnalysisAnnouncements(report.into()))
|
||||
}
|
||||
|
||||
CaCommand::BgpAnalysisRoas(handle) => {
|
||||
let uri = format!("api/v1/cas/{}/routes/analysis/full", handle);
|
||||
let report: BgpAnalysisReport = self.get_json(&uri).await?;
|
||||
Ok(ApiResponse::BgpAnalysisRoas(report.into()))
|
||||
}
|
||||
|
||||
CaCommand::Show(handle) => {
|
||||
let uri = format!("api/v1/cas/{}", handle);
|
||||
let ca_info = self.get_json(&uri).await?;
|
||||
|
||||
+184
-147
@@ -251,7 +251,11 @@ impl Options {
|
||||
Arg::with_name("rrdp")
|
||||
.long("rrdp")
|
||||
.value_name("uri")
|
||||
.help("Specify the base https URI for your RRDP (excluding notify.xml), must end with '/'")
|
||||
.help(
|
||||
"Specify the base https URI for your RRDP (excluding notification.xml), \
|
||||
must \
|
||||
end with '/'",
|
||||
)
|
||||
.required(true),
|
||||
)
|
||||
}
|
||||
@@ -371,30 +375,22 @@ impl Options {
|
||||
}
|
||||
|
||||
fn make_cas_children_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let sub = SubCommand::with_name("add").about("Add a child to a CA.");
|
||||
let mut sub = SubCommand::with_name("add").about("Add a child to a CA.");
|
||||
|
||||
let mut embedded =
|
||||
SubCommand::with_name("embedded").about("Add a child in *this* Krill server");
|
||||
embedded = Self::add_general_args(embedded);
|
||||
embedded = Self::add_my_ca_arg(embedded);
|
||||
embedded = Self::add_child_arg(embedded);
|
||||
embedded = Self::add_child_resource_args(embedded);
|
||||
|
||||
let mut remote = SubCommand::with_name("remote")
|
||||
.about("Add a remote child, and return the parent response");
|
||||
remote = Self::add_general_args(remote);
|
||||
remote = Self::add_my_ca_arg(remote);
|
||||
remote = Self::add_child_arg(remote);
|
||||
remote = Self::add_child_resource_args(remote);
|
||||
let remote = remote.arg(
|
||||
Arg::with_name("rfc8183")
|
||||
.long("rfc8183")
|
||||
.help("The RFC8183 Child Request XML file.")
|
||||
sub = Self::add_general_args(sub);
|
||||
sub = Self::add_my_ca_arg(sub);
|
||||
sub = Self::add_child_arg(sub);
|
||||
sub = Self::add_child_resource_args(sub);
|
||||
let sub = sub.arg(
|
||||
Arg::with_name("request")
|
||||
.long("request")
|
||||
.short("r")
|
||||
.help("The location of the RFC8183 Child Request XML file.")
|
||||
.value_name("<XML file>")
|
||||
.required(true),
|
||||
);
|
||||
|
||||
app.subcommand(sub.subcommand(embedded).subcommand(remote))
|
||||
app.subcommand(sub)
|
||||
}
|
||||
|
||||
fn make_cas_children_update_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
@@ -469,40 +465,35 @@ impl Options {
|
||||
}
|
||||
|
||||
fn make_cas_parents_add_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let sub = SubCommand::with_name("add").about("Add a parent to this CA.");
|
||||
|
||||
let mut embedded = SubCommand::with_name("embedded")
|
||||
.about("Add a parent that you manage in *this* Krill server");
|
||||
embedded = Self::add_general_args(embedded);
|
||||
embedded = Self::add_my_ca_arg(embedded);
|
||||
embedded = Self::add_parent_arg(embedded);
|
||||
|
||||
let mut remote = SubCommand::with_name("remote").about("Add a remote parent");
|
||||
remote = Self::add_general_args(remote);
|
||||
remote = Self::add_my_ca_arg(remote);
|
||||
remote = Self::add_parent_arg(remote);
|
||||
remote = remote.arg(
|
||||
Arg::with_name("rfc8183")
|
||||
.long("rfc8183")
|
||||
.help("The RFC8183 Parent Response XML")
|
||||
.value_name("<XML file>")
|
||||
.required(true),
|
||||
);
|
||||
|
||||
app.subcommand(sub.subcommand(remote).subcommand(embedded))
|
||||
}
|
||||
|
||||
fn make_cas_parents_update_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let mut sub =
|
||||
SubCommand::with_name("update").about("Update an existing remote parent of this CA.");
|
||||
let mut sub = SubCommand::with_name("add").about("Add a parent to this CA.");
|
||||
|
||||
sub = Self::add_general_args(sub);
|
||||
sub = Self::add_my_ca_arg(sub);
|
||||
sub = Self::add_parent_arg(sub);
|
||||
sub = sub.arg(
|
||||
Arg::with_name("rfc8183")
|
||||
.long("rfc8183")
|
||||
.help("The RFC8183 Parent Response XML")
|
||||
Arg::with_name("response")
|
||||
.long("response")
|
||||
.short("r")
|
||||
.help("The location of the RFC8183 Parent Response XML file.")
|
||||
.value_name("<XML file>")
|
||||
.required(true),
|
||||
);
|
||||
|
||||
app.subcommand(sub)
|
||||
}
|
||||
|
||||
fn make_cas_parents_update_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let mut sub =
|
||||
SubCommand::with_name("update").about("Update an existing parent of this CA.");
|
||||
|
||||
sub = Self::add_general_args(sub);
|
||||
sub = Self::add_my_ca_arg(sub);
|
||||
sub = Self::add_parent_arg(sub);
|
||||
sub = sub.arg(
|
||||
Arg::with_name("response")
|
||||
.long("response")
|
||||
.short("r")
|
||||
.help("The location of the RFC8183 Parent Response XML file.")
|
||||
.value_name("<XML file>")
|
||||
.required(true),
|
||||
);
|
||||
@@ -607,11 +598,48 @@ impl Options {
|
||||
app.subcommand(sub)
|
||||
}
|
||||
|
||||
fn make_cas_routes_bgp_full_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let mut sub = SubCommand::with_name("full").about("Show full report.");
|
||||
|
||||
sub = Self::add_general_args(sub);
|
||||
sub = Self::add_my_ca_arg(sub);
|
||||
app.subcommand(sub)
|
||||
}
|
||||
|
||||
fn make_cas_routes_bgp_announcements_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let mut sub =
|
||||
SubCommand::with_name("announcements").about("Show announcement centric report.");
|
||||
|
||||
sub = Self::add_general_args(sub);
|
||||
sub = Self::add_my_ca_arg(sub);
|
||||
app.subcommand(sub)
|
||||
}
|
||||
|
||||
fn make_cas_routes_bgp_roas_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let mut sub = SubCommand::with_name("roas").about("Show ROA centric report.");
|
||||
|
||||
sub = Self::add_general_args(sub);
|
||||
sub = Self::add_my_ca_arg(sub);
|
||||
app.subcommand(sub)
|
||||
}
|
||||
|
||||
fn make_cas_routes_bgp_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let mut sub = SubCommand::with_name("bgp")
|
||||
.about("Show current authorizations in relation to known announcements.");
|
||||
|
||||
sub = Self::make_cas_routes_bgp_full_sc(sub);
|
||||
sub = Self::make_cas_routes_bgp_announcements_sc(sub);
|
||||
sub = Self::make_cas_routes_bgp_roas_sc(sub);
|
||||
|
||||
app.subcommand(sub)
|
||||
}
|
||||
|
||||
fn make_cas_routes_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let mut sub = SubCommand::with_name("roas").about("Manage ROAs for your CA.");
|
||||
|
||||
sub = Self::make_cas_routes_list_sc(sub);
|
||||
sub = Self::make_cas_routes_update_sc(sub);
|
||||
sub = Self::make_cas_routes_bgp_sc(sub);
|
||||
|
||||
app.subcommand(sub)
|
||||
}
|
||||
@@ -647,25 +675,17 @@ impl Options {
|
||||
let mut sub =
|
||||
SubCommand::with_name("update").about("Change which repository this CA uses.");
|
||||
|
||||
let mut embedded =
|
||||
SubCommand::with_name("embedded").about("Use the embedded server in krill");
|
||||
embedded = Self::add_general_args(embedded);
|
||||
embedded = Self::add_my_ca_arg(embedded);
|
||||
|
||||
let mut remote = SubCommand::with_name("remote").about("Use a remote server (RECOMMENDED)");
|
||||
remote = Self::add_general_args(remote);
|
||||
remote = Self::add_my_ca_arg(remote);
|
||||
remote = remote.arg(
|
||||
Arg::with_name("rfc8183")
|
||||
sub = Self::add_general_args(sub);
|
||||
sub = Self::add_my_ca_arg(sub);
|
||||
sub = sub.arg(
|
||||
Arg::with_name("response")
|
||||
.value_name("file")
|
||||
.long("rfc8183")
|
||||
.help("File containing the RFC8183 XML. Defaults to reading from STDIN")
|
||||
.long("response")
|
||||
.short("r")
|
||||
.help("The location of the RFC8183 Publisher Response XML file. Defaults to reading from STDIN")
|
||||
.required(false),
|
||||
);
|
||||
|
||||
sub = sub.subcommand(embedded);
|
||||
sub = sub.subcommand(remote);
|
||||
|
||||
app.subcommand(sub)
|
||||
}
|
||||
|
||||
@@ -730,20 +750,23 @@ impl Options {
|
||||
let mut sub = SubCommand::with_name("add").about("Add a publisher.");
|
||||
sub = Self::add_general_args(sub);
|
||||
|
||||
sub = sub.arg(
|
||||
Arg::with_name("rfc8183")
|
||||
.value_name("file")
|
||||
.long("rfc8183")
|
||||
.help("RFC8183 Publisher Request XML file containing a certificate (tag is ignored)")
|
||||
.required(true)
|
||||
).arg(
|
||||
Arg::with_name("publisher")
|
||||
.value_name("handle")
|
||||
.short("p")
|
||||
.long("publisher")
|
||||
.help("Override the publisher handle in the XML.")
|
||||
.required(false),
|
||||
);
|
||||
sub = sub
|
||||
.arg(
|
||||
Arg::with_name("request")
|
||||
.value_name("file")
|
||||
.long("request")
|
||||
.short("r")
|
||||
.help("The location of the RFC8183 Publisher Request XML file.")
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("publisher")
|
||||
.value_name("handle")
|
||||
.short("p")
|
||||
.long("publisher")
|
||||
.help("Override the publisher handle in the XML.")
|
||||
.required(false),
|
||||
);
|
||||
|
||||
app.subcommand(sub)
|
||||
}
|
||||
@@ -763,8 +786,8 @@ impl Options {
|
||||
}
|
||||
|
||||
fn make_publishers_response_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let mut sub = SubCommand::with_name("response")
|
||||
.about("Show RFC8183 Repository Response XML.");
|
||||
let mut sub =
|
||||
SubCommand::with_name("response").about("Show RFC8183 Repository Response XML.");
|
||||
sub = Self::add_general_args(sub);
|
||||
sub = Self::add_publisher_arg(sub);
|
||||
app.subcommand(sub)
|
||||
@@ -810,7 +833,8 @@ impl Options {
|
||||
}
|
||||
|
||||
fn make_health_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let health = SubCommand::with_name("health").about("Perform an authenticated health check.");
|
||||
let health =
|
||||
SubCommand::with_name("health").about("Perform an authenticated health check.");
|
||||
let health = Self::add_general_args(health);
|
||||
app.subcommand(health)
|
||||
}
|
||||
@@ -1031,10 +1055,12 @@ impl Options {
|
||||
Ok(Options::make(general_args, command))
|
||||
}
|
||||
|
||||
fn parse_matches_cas_children_add_general(
|
||||
matches: &ArgMatches,
|
||||
rfc8183_opt: Option<rfc8183::ChildRequest>,
|
||||
) -> Result<Options, Error> {
|
||||
fn parse_matches_cas_children_add(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
let path = matches.value_of("request").unwrap();
|
||||
let bytes = Self::read_file_arg(path)?;
|
||||
let request = rfc8183::ChildRequest::validate(bytes.as_ref())?;
|
||||
let auth_request = ChildAuthRequest::Rfc8183(request);
|
||||
|
||||
let general_args = GeneralArgs::from_matches(matches)?;
|
||||
let my_ca = Self::parse_my_ca(matches)?;
|
||||
|
||||
@@ -1044,29 +1070,11 @@ impl Options {
|
||||
let resources =
|
||||
Self::parse_resource_args(matches)?.ok_or_else(|| Error::MissingResources)?;
|
||||
|
||||
let auth_request = match rfc8183_opt {
|
||||
Some(rfc8183) => ChildAuthRequest::Rfc8183(rfc8183),
|
||||
None => ChildAuthRequest::Embedded,
|
||||
};
|
||||
|
||||
let child_request = AddChildRequest::new(child, resources, auth_request);
|
||||
let command = Command::CertAuth(CaCommand::ChildAdd(my_ca, child_request));
|
||||
Ok(Options::make(general_args, command))
|
||||
}
|
||||
|
||||
fn parse_matches_cas_children_add(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
if let Some(m) = matches.subcommand_matches("embedded") {
|
||||
Self::parse_matches_cas_children_add_general(m, None)
|
||||
} else if let Some(m) = matches.subcommand_matches("remote") {
|
||||
let path = m.value_of("rfc8183").unwrap();
|
||||
let bytes = Self::read_file_arg(path)?;
|
||||
let request = rfc8183::ChildRequest::validate(bytes.as_ref())?;
|
||||
Self::parse_matches_cas_children_add_general(m, Some(request))
|
||||
} else {
|
||||
Err(Error::UnrecognisedSubCommand)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_matches_cas_children_update(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
let general_args = GeneralArgs::from_matches(matches)?;
|
||||
let my_ca = Self::parse_my_ca(matches)?;
|
||||
@@ -1149,38 +1157,23 @@ impl Options {
|
||||
Ok(Options::make(general_args, command))
|
||||
}
|
||||
|
||||
fn parse_matches_cas_parents_add_general(
|
||||
matches: &ArgMatches,
|
||||
response_opt: Option<rfc8183::ParentResponse>,
|
||||
) -> Result<Options, Error> {
|
||||
fn parse_matches_cas_parents_add(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
let path = matches.value_of("response").unwrap();
|
||||
let bytes = Self::read_file_arg(path)?;
|
||||
let response = rfc8183::ParentResponse::validate(bytes.as_ref())?;
|
||||
|
||||
let general_args = GeneralArgs::from_matches(matches)?;
|
||||
let my_ca = Self::parse_my_ca(matches)?;
|
||||
|
||||
let parent = matches.value_of("parent").unwrap();
|
||||
let parent = Handle::from_str(parent).map_err(|_| Error::InvalidHandle)?;
|
||||
let contact = match response_opt {
|
||||
Some(response) => ParentCaContact::for_rfc6492(response),
|
||||
None => ParentCaContact::Embedded,
|
||||
};
|
||||
let contact = ParentCaContact::for_rfc6492(response);
|
||||
let parent_req = ParentCaReq::new(parent, contact);
|
||||
|
||||
let command = Command::CertAuth(CaCommand::AddParent(my_ca, parent_req));
|
||||
Ok(Options::make(general_args, command))
|
||||
}
|
||||
|
||||
fn parse_matches_cas_parents_add(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
if let Some(m) = matches.subcommand_matches("embedded") {
|
||||
Self::parse_matches_cas_parents_add_general(m, None)
|
||||
} else if let Some(m) = matches.subcommand_matches("remote") {
|
||||
let path = m.value_of("rfc8183").unwrap();
|
||||
let bytes = Self::read_file_arg(path)?;
|
||||
let response = rfc8183::ParentResponse::validate(bytes.as_ref())?;
|
||||
Self::parse_matches_cas_parents_add_general(m, Some(response))
|
||||
} else {
|
||||
Err(Error::UnrecognisedSubCommand)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_matches_cas_parents_update(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
let general_args = GeneralArgs::from_matches(matches)?;
|
||||
let my_ca = Self::parse_my_ca(matches)?;
|
||||
@@ -1188,7 +1181,7 @@ impl Options {
|
||||
let parent = matches.value_of("parent").unwrap();
|
||||
let parent = Handle::from_str(parent).map_err(|_| Error::InvalidHandle)?;
|
||||
|
||||
let path = matches.value_of("rfc8183").unwrap();
|
||||
let path = matches.value_of("response").unwrap();
|
||||
let bytes = Self::read_file_arg(path)?;
|
||||
let response = rfc8183::ParentResponse::validate(bytes.as_ref())?;
|
||||
|
||||
@@ -1287,11 +1280,52 @@ impl Options {
|
||||
Ok(Options::make(general_args, command))
|
||||
}
|
||||
|
||||
fn parse_matches_cas_routes_bgp_full(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
let general_args = GeneralArgs::from_matches(matches)?;
|
||||
let my_ca = Self::parse_my_ca(matches)?;
|
||||
Ok(Options::make(
|
||||
general_args,
|
||||
Command::CertAuth(CaCommand::BgpAnalysisFull(my_ca)),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_matches_cas_routes_bgp_announcements(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
let general_args = GeneralArgs::from_matches(matches)?;
|
||||
let my_ca = Self::parse_my_ca(matches)?;
|
||||
Ok(Options::make(
|
||||
general_args,
|
||||
Command::CertAuth(CaCommand::BgpAnalysisAnnouncements(my_ca)),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_matches_cas_routes_bgp_roas(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
let general_args = GeneralArgs::from_matches(matches)?;
|
||||
let my_ca = Self::parse_my_ca(matches)?;
|
||||
Ok(Options::make(
|
||||
general_args,
|
||||
Command::CertAuth(CaCommand::BgpAnalysisRoas(my_ca)),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_matches_cas_routes_bgp(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
if let Some(m) = matches.subcommand_matches("full") {
|
||||
Self::parse_matches_cas_routes_bgp_full(m)
|
||||
} else if let Some(m) = matches.subcommand_matches("announcements") {
|
||||
Self::parse_matches_cas_routes_bgp_announcements(m)
|
||||
} else if let Some(m) = matches.subcommand_matches("roas") {
|
||||
Self::parse_matches_cas_routes_bgp_roas(m)
|
||||
} else {
|
||||
Err(Error::UnrecognisedSubCommand)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_matches_cas_routes(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
if let Some(m) = matches.subcommand_matches("list") {
|
||||
Self::parse_matches_cas_routes_list(m)
|
||||
} else if let Some(m) = matches.subcommand_matches("update") {
|
||||
Self::parse_matches_cas_routes_update(m)
|
||||
} else if let Some(m) = matches.subcommand_matches("bgp") {
|
||||
Self::parse_matches_cas_routes_bgp(m)
|
||||
} else {
|
||||
Err(Error::UnrecognisedSubCommand)
|
||||
}
|
||||
@@ -1325,26 +1359,16 @@ impl Options {
|
||||
}
|
||||
|
||||
fn parse_matches_cas_repo_update(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
if let Some(matches) = matches.subcommand_matches("embedded") {
|
||||
let general_args = GeneralArgs::from_matches(matches)?;
|
||||
let my_ca = Self::parse_my_ca(matches)?;
|
||||
let update = RepositoryUpdate::embedded();
|
||||
let command = Command::CertAuth(CaCommand::RepoUpdate(my_ca, update));
|
||||
Ok(Options::make(general_args, command))
|
||||
} else if let Some(matches) = matches.subcommand_matches("remote") {
|
||||
let general_args = GeneralArgs::from_matches(matches)?;
|
||||
let my_ca = Self::parse_my_ca(matches)?;
|
||||
let general_args = GeneralArgs::from_matches(matches)?;
|
||||
let my_ca = Self::parse_my_ca(matches)?;
|
||||
|
||||
let path = matches.value_of("rfc8183").unwrap();
|
||||
let bytes = Self::read_file_arg(path)?;
|
||||
let response = rfc8183::RepositoryResponse::validate(bytes.as_ref())?;
|
||||
let path = matches.value_of("response").unwrap();
|
||||
let bytes = Self::read_file_arg(path)?;
|
||||
let response = rfc8183::RepositoryResponse::validate(bytes.as_ref())?;
|
||||
|
||||
let update = RepositoryUpdate::rfc8181(response);
|
||||
let command = Command::CertAuth(CaCommand::RepoUpdate(my_ca, update));
|
||||
Ok(Options::make(general_args, command))
|
||||
} else {
|
||||
Err(Error::UnrecognisedSubCommand)
|
||||
}
|
||||
let update = RepositoryUpdate::rfc8181(response);
|
||||
let command = Command::CertAuth(CaCommand::RepoUpdate(my_ca, update));
|
||||
Ok(Options::make(general_args, command))
|
||||
}
|
||||
|
||||
fn parse_matches_cas_repo(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
@@ -1399,7 +1423,7 @@ impl Options {
|
||||
fn parse_matches_publishers_add(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
let general_args = GeneralArgs::from_matches(matches)?;
|
||||
|
||||
let path = matches.value_of("rfc8183").unwrap();
|
||||
let path = matches.value_of("request").unwrap();
|
||||
let path = PathBuf::from(path);
|
||||
let bytes = file::read(&path)?;
|
||||
let mut req = rfc8183::PublisherRequest::validate(bytes.as_ref())?;
|
||||
@@ -1618,12 +1642,28 @@ pub enum CaCommand {
|
||||
#[display(fmt = "activate key roll for ca: '{}'", _0)]
|
||||
KeyRollActivate(Handle),
|
||||
|
||||
// Authorizations
|
||||
#[display(fmt = "list ROAS for ca: '{}'", _0)]
|
||||
RouteAuthorizationsList(Handle),
|
||||
|
||||
#[display(fmt = "Update ROAS for ca: '{}' -> {}", _0, _1)]
|
||||
RouteAuthorizationsUpdate(Handle, RoaDefinitionUpdates),
|
||||
|
||||
#[display(fmt = "Show detailed ROA vs BGP analysis for ca: '{}'", _0)]
|
||||
BgpAnalysisFull(Handle),
|
||||
|
||||
#[display(
|
||||
fmt = "Show announcement centric summary of ROA vs BGP analysis for ca: '{}'",
|
||||
_0
|
||||
)]
|
||||
BgpAnalysisAnnouncements(Handle),
|
||||
|
||||
#[display(
|
||||
fmt = "Show ROA centric summary of ROA vs BGP analysis for ca: '{}'",
|
||||
_0
|
||||
)]
|
||||
BgpAnalysisRoas(Handle),
|
||||
|
||||
// Show details for this CA
|
||||
#[display(fmt = "Show details for ca: '{}'", _0)]
|
||||
Show(Handle),
|
||||
@@ -1824,9 +1864,6 @@ pub enum Error {
|
||||
#[display(fmt = "You must specify resources when adding a CA (--asn, --ipv4, --ipv6)")]
|
||||
MissingResources,
|
||||
|
||||
#[display(fmt = "You must specify either --embedded or --rfc8183 when adding a child")]
|
||||
MissingChildAuth,
|
||||
|
||||
#[display(fmt = "Invalid ID cert for child.")]
|
||||
InvalidChildIdCert,
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::commons::api::{
|
||||
ParentCaContact, PublisherDetails, PublisherList, RepositoryContact, RoaDefinition, ServerInfo,
|
||||
StoredEffect,
|
||||
};
|
||||
use crate::commons::bgp::{AnnouncementReport, BgpAnalysisReport, RoaReport};
|
||||
use crate::commons::eventsourcing::WithStorableDetails;
|
||||
use crate::commons::remote::api::ClientInfo;
|
||||
use crate::commons::remote::rfc8183;
|
||||
@@ -30,6 +31,9 @@ pub enum ApiResponse {
|
||||
CertAuthAction(CaCommandDetails),
|
||||
CertAuths(CertAuthList),
|
||||
RouteAuthorizations(Vec<RoaDefinition>),
|
||||
BgpAnalysisFull(BgpAnalysisReport),
|
||||
BgpAnalysisAnnouncements(AnnouncementReport),
|
||||
BgpAnalysisRoas(RoaReport),
|
||||
|
||||
ParentCaContact(ParentCaContact),
|
||||
|
||||
@@ -69,6 +73,9 @@ impl ApiResponse {
|
||||
ApiResponse::CertAuthIssues(issues) => Ok(Some(issues.report(fmt)?)),
|
||||
ApiResponse::AllCertAuthIssues(issues) => Ok(Some(issues.report(fmt)?)),
|
||||
ApiResponse::RouteAuthorizations(auths) => Ok(Some(auths.report(fmt)?)),
|
||||
ApiResponse::BgpAnalysisFull(table) => Ok(Some(table.report(fmt)?)),
|
||||
ApiResponse::BgpAnalysisAnnouncements(summary) => Ok(Some(summary.report(fmt)?)),
|
||||
ApiResponse::BgpAnalysisRoas(summary) => Ok(Some(summary.report(fmt)?)),
|
||||
ApiResponse::ParentCaContact(contact) => Ok(Some(contact.report(fmt)?)),
|
||||
ApiResponse::ChildInfo(info) => Ok(Some(info.report(fmt)?)),
|
||||
ApiResponse::PublisherList(list) => Ok(Some(list.report(fmt)?)),
|
||||
@@ -407,6 +414,24 @@ impl Report for Vec<RoaDefinition> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Report for BgpAnalysisReport {
|
||||
fn text(&self) -> Result<String, ReportError> {
|
||||
Ok(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Report for AnnouncementReport {
|
||||
fn text(&self) -> Result<String, ReportError> {
|
||||
Ok(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Report for RoaReport {
|
||||
fn text(&self) -> Result<String, ReportError> {
|
||||
Ok(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Report for CaRepoDetails {
|
||||
fn text(&self) -> Result<String, ReportError> {
|
||||
let mut res = String::new();
|
||||
|
||||
+52
-1
@@ -1891,13 +1891,15 @@ impl CertAuthIssues {
|
||||
pub struct CertAuthStats {
|
||||
roa_count: usize,
|
||||
child_count: usize,
|
||||
bgp_stats: BgpStats,
|
||||
}
|
||||
|
||||
impl CertAuthStats {
|
||||
pub fn new(roa_count: usize, child_count: usize) -> Self {
|
||||
pub fn new(roa_count: usize, child_count: usize, bgp_stats: BgpStats) -> Self {
|
||||
CertAuthStats {
|
||||
roa_count,
|
||||
child_count,
|
||||
bgp_stats,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1908,6 +1910,55 @@ impl CertAuthStats {
|
||||
pub fn child_count(&self) -> usize {
|
||||
self.child_count
|
||||
}
|
||||
|
||||
pub fn bgp_stats(&self) -> &BgpStats {
|
||||
&self.bgp_stats
|
||||
}
|
||||
}
|
||||
|
||||
//------------ BgpStats ------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct BgpStats {
|
||||
pub announcements_valid: usize,
|
||||
pub announcements_invalid_asn: usize,
|
||||
pub announcements_invalid_length: usize,
|
||||
pub announcements_not_found: usize,
|
||||
pub roas_stale: usize,
|
||||
}
|
||||
|
||||
impl Default for BgpStats {
|
||||
fn default() -> Self {
|
||||
BgpStats {
|
||||
announcements_valid: 0,
|
||||
announcements_invalid_asn: 0,
|
||||
announcements_invalid_length: 0,
|
||||
announcements_not_found: 0,
|
||||
roas_stale: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BgpStats {
|
||||
pub fn increment_valid(&mut self) {
|
||||
self.announcements_valid += 1;
|
||||
}
|
||||
|
||||
pub fn increment_invalid_asn(&mut self) {
|
||||
self.announcements_invalid_asn += 1;
|
||||
}
|
||||
|
||||
pub fn increment_invalid_length(&mut self) {
|
||||
self.announcements_invalid_length += 1;
|
||||
}
|
||||
|
||||
pub fn increment_not_found(&mut self) {
|
||||
self.announcements_not_found += 1;
|
||||
}
|
||||
|
||||
pub fn increment_stale(&mut self) {
|
||||
self.roas_stale += 1;
|
||||
}
|
||||
}
|
||||
|
||||
//------------ ResSetErr -----------------------------------------------------
|
||||
|
||||
+170
-14
@@ -1,3 +1,4 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::net::IpAddr;
|
||||
@@ -15,7 +16,7 @@ use crate::daemon::ca::RouteAuthorizationUpdates;
|
||||
|
||||
/// This type defines the definition of a Route Origin Authorization (ROA), i.e.
|
||||
/// the originating asn, IPv4 or IPv6 prefix, and optionally a max length.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
#[derive(Clone, Copy, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct RoaDefinition {
|
||||
asn: AsNumber,
|
||||
prefix: TypedPrefix,
|
||||
@@ -32,6 +33,14 @@ impl RoaDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn explicit_max_length(self) -> Self {
|
||||
RoaDefinition {
|
||||
asn: self.asn,
|
||||
prefix: self.prefix,
|
||||
max_length: Some(self.effective_max_length()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn asn(&self) -> AsNumber {
|
||||
self.asn
|
||||
}
|
||||
@@ -61,6 +70,13 @@ impl RoaDefinition {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the this definition includes the other definition.
|
||||
pub fn includes(&self, other: &RoaDefinition) -> bool {
|
||||
self.asn == other.asn
|
||||
&& self.prefix.matching_or_less_specific(&other.prefix)
|
||||
&& self.effective_max_length() >= other.effective_max_length()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RoaDefinition {
|
||||
@@ -99,6 +115,12 @@ impl FromStr for RoaDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for RoaDefinition {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", &self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RoaDefinition {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.max_length {
|
||||
@@ -108,6 +130,36 @@ impl fmt::Display for RoaDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for RoaDefinition {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let mut ordering = self.prefix.cmp(&other.prefix);
|
||||
|
||||
if ordering == Ordering::Equal {
|
||||
ordering = self
|
||||
.effective_max_length()
|
||||
.cmp(&other.effective_max_length());
|
||||
}
|
||||
|
||||
if ordering == Ordering::Equal {
|
||||
ordering = self.asn.cmp(&other.asn);
|
||||
}
|
||||
|
||||
ordering
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for RoaDefinition {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<TypedPrefix> for RoaDefinition {
|
||||
fn as_ref(&self) -> &TypedPrefix {
|
||||
&self.prefix
|
||||
}
|
||||
}
|
||||
|
||||
//------------ RouteAuthorizationUpdates -----------------------------------
|
||||
|
||||
/// This type defines a delta of Route Authorizations, i.e. additions or removals
|
||||
@@ -116,7 +168,7 @@ impl fmt::Display for RoaDefinition {
|
||||
/// on its resource certificates.
|
||||
///
|
||||
/// Multiple updates are sent as a single delta, because it's important that
|
||||
/// all authorisations for a given prefix are published together in order to
|
||||
/// all authorizations for a given prefix are published together in order to
|
||||
/// avoid invalidating announcements.
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct RoaDefinitionUpdates {
|
||||
@@ -222,7 +274,7 @@ impl From<RouteAuthorizationUpdates> for RoaDefinitionUpdates {
|
||||
}
|
||||
|
||||
//------------ TypedPrefix -------------------------------------------------
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
pub enum TypedPrefix {
|
||||
V4(Ipv4Prefix),
|
||||
V6(Ipv6Prefix),
|
||||
@@ -239,6 +291,25 @@ impl TypedPrefix {
|
||||
TypedPrefix::V6(v6) => IpAddr::V6(v6.0.to_v6()),
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_type(&self, other: &TypedPrefix) -> bool {
|
||||
match &self {
|
||||
TypedPrefix::V4(_) => match other {
|
||||
TypedPrefix::V4(_) => true,
|
||||
TypedPrefix::V6(_) => false,
|
||||
},
|
||||
TypedPrefix::V6(_) => match other {
|
||||
TypedPrefix::V4(_) => false,
|
||||
TypedPrefix::V6(_) => true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matching_or_less_specific(&self, other: &TypedPrefix) -> bool {
|
||||
self.matches_type(other)
|
||||
&& self.prefix().min().le(&other.prefix().min())
|
||||
&& self.prefix().max().ge(&other.prefix().max())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TypedPrefix {
|
||||
@@ -259,6 +330,12 @@ impl FromStr for TypedPrefix {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TypedPrefix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", &self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TypedPrefix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
@@ -268,6 +345,22 @@ impl fmt::Display for TypedPrefix {
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for TypedPrefix {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let mut ordering = self.addr().cmp(&other.addr());
|
||||
if ordering == Ordering::Equal {
|
||||
ordering = self.addr_len().cmp(&other.addr_len())
|
||||
}
|
||||
ordering
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for TypedPrefix {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Prefix> for TypedPrefix {
|
||||
fn as_ref(&self) -> &Prefix {
|
||||
match self {
|
||||
@@ -326,28 +419,52 @@ impl From<TypedPrefix> for ResourceSet {
|
||||
}
|
||||
|
||||
//------------ Ipv4Prefix --------------------------------------------------
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
pub struct Ipv4Prefix(Prefix);
|
||||
|
||||
impl AsRef<Prefix> for Ipv4Prefix {
|
||||
fn as_ref(&self) -> &Prefix {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Ipv4Prefix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}/{}", self.0.to_v4(), self.0.addr_len())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Ipv4Prefix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", &self)
|
||||
}
|
||||
}
|
||||
|
||||
//------------ Ipv6Prefix --------------------------------------------------
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
pub struct Ipv6Prefix(Prefix);
|
||||
|
||||
impl AsRef<Prefix> for Ipv6Prefix {
|
||||
fn as_ref(&self) -> &Prefix {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Ipv6Prefix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}/{}", self.0.to_v6(), self.0.addr_len())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Ipv6Prefix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", &self)
|
||||
}
|
||||
}
|
||||
|
||||
//------------ AsNumber ----------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
#[derive(Clone, Copy, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct AsNumber(u32);
|
||||
|
||||
impl AsNumber {
|
||||
@@ -372,12 +489,30 @@ impl FromStr for AsNumber {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for AsNumber {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", &self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AsNumber {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for AsNumber {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.0.cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for AsNumber {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
//------------ AuthorizationFmtError -------------------------------------
|
||||
|
||||
#[derive(Clone, Debug, Display, Eq, PartialEq)]
|
||||
@@ -388,10 +523,10 @@ pub enum AuthorizationFmtError {
|
||||
#[display(fmt = "Invalid asn in string: {}", _0)]
|
||||
Asn(String),
|
||||
|
||||
#[display(fmt = "Invalid authorisation string: {}", _0)]
|
||||
#[display(fmt = "Invalid authorization string: {}", _0)]
|
||||
Auth(String),
|
||||
|
||||
#[display(fmt = "Invalid authorisation delta string: {}", _0)]
|
||||
#[display(fmt = "Invalid authorization delta string: {}", _0)]
|
||||
Delta(String),
|
||||
}
|
||||
|
||||
@@ -419,6 +554,8 @@ impl AuthorizationFmtError {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::test::definition;
|
||||
|
||||
#[test]
|
||||
fn parse_delta() {
|
||||
let delta = concat!(
|
||||
@@ -432,11 +569,11 @@ mod tests {
|
||||
|
||||
let expected = {
|
||||
let mut added = HashSet::new();
|
||||
added.insert(RoaDefinition::from_str("192.168.0.0/16 => 64496").unwrap());
|
||||
added.insert(RoaDefinition::from_str("192.168.1.0/24 => 64496").unwrap());
|
||||
added.insert(definition("192.168.0.0/16 => 64496"));
|
||||
added.insert(definition("192.168.1.0/24 => 64496"));
|
||||
|
||||
let mut removed = HashSet::new();
|
||||
removed.insert(RoaDefinition::from_str("192.168.3.0/24 => 64496").unwrap());
|
||||
removed.insert(definition("192.168.3.0/24 => 64496"));
|
||||
RoaDefinitionUpdates::new(added, removed)
|
||||
};
|
||||
|
||||
@@ -455,12 +592,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn normalize_roa_definition_json() {
|
||||
let def = RoaDefinition::from_str("192.168.0.0/16 => 64496").unwrap();
|
||||
let def = definition("192.168.0.0/16 => 64496");
|
||||
let json = serde_json::to_string(&def).unwrap();
|
||||
let expected = "{\"asn\":64496,\"prefix\":\"192.168.0.0/16\"}";
|
||||
assert_eq!(json, expected);
|
||||
|
||||
let def = RoaDefinition::from_str("192.168.0.0/16-24 => 64496").unwrap();
|
||||
let def = definition("192.168.0.0/16-24 => 64496");
|
||||
let json = serde_json::to_string(&def).unwrap();
|
||||
let expected = "{\"asn\":64496,\"prefix\":\"192.168.0.0/16\",\"max_length\":24}";
|
||||
assert_eq!(json, expected);
|
||||
@@ -469,7 +606,7 @@ mod tests {
|
||||
#[test]
|
||||
fn serde_roa_definition() {
|
||||
fn parse_ser_de_print_definition(s: &str) {
|
||||
let def = RoaDefinition::from_str(s).unwrap();
|
||||
let def = definition(s);
|
||||
let ser = serde_json::to_string(&def).unwrap();
|
||||
let de = serde_json::from_str(&ser).unwrap();
|
||||
assert_eq!(def, de);
|
||||
@@ -508,4 +645,23 @@ mod tests {
|
||||
invalid_max_length("2001:db8::/32-31 => 64496");
|
||||
invalid_max_length("2001:db8::/32-129 => 64496");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roa_includes() {
|
||||
let covering = definition("192.168.0.0/16-20 => 64496");
|
||||
|
||||
let included_no_ml = definition("192.168.0.0/16 => 64496");
|
||||
let included_more_specific = definition("192.168.0.0/20 => 64496");
|
||||
|
||||
let allowing_more_specific = definition("192.168.0.0/16-24 => 64496");
|
||||
let more_specific = definition("192.168.3.0/24 => 64496");
|
||||
let other_asn = definition("192.168.3.0/24 => 64497");
|
||||
|
||||
assert!(covering.includes(&included_no_ml));
|
||||
assert!(covering.includes(&included_more_specific));
|
||||
|
||||
assert!(!covering.includes(&more_specific));
|
||||
assert!(!covering.includes(&allowing_more_specific));
|
||||
assert!(!covering.includes(&other_asn));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
use std::env;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use chrono::Duration;
|
||||
|
||||
use rpki::x509::Time;
|
||||
|
||||
use crate::commons::api::{ResourceSet, RoaDefinition};
|
||||
use crate::commons::bgp::{
|
||||
make_roa_tree, make_validated_announcement_tree, Announcement, AnnouncementValidity,
|
||||
Announcements, BgpAnalysisEntry, BgpAnalysisReport, IpRange, RisDumpError, RisDumpLoader,
|
||||
ValidatedAnnouncement,
|
||||
};
|
||||
use crate::constants::{BGP_RIS_REFRESH_MINUTES, KRILL_ENV_TEST_ANN};
|
||||
|
||||
//------------ BgpAnalyser -------------------------------------------------
|
||||
|
||||
/// This type helps analyse ROAs vs BGP and vice versa.
|
||||
pub struct BgpAnalyser {
|
||||
dumploader: Option<RisDumpLoader>,
|
||||
seen: RwLock<Announcements>,
|
||||
}
|
||||
|
||||
impl BgpAnalyser {
|
||||
pub fn new(ris_enabled: bool, ris_v4_uri: &str, ris_v6_uri: &str) -> Self {
|
||||
if env::var(KRILL_ENV_TEST_ANN).is_ok() {
|
||||
Self::with_test_announcements()
|
||||
} else {
|
||||
let dumploader = if ris_enabled {
|
||||
Some(RisDumpLoader::new(ris_v4_uri, ris_v6_uri))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
BgpAnalyser {
|
||||
dumploader,
|
||||
seen: RwLock::new(Announcements::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update(&self) -> Result<bool, BgpAnalyserError> {
|
||||
if let Some(loader) = &self.dumploader {
|
||||
let mut seen = self.seen.write().unwrap();
|
||||
if let Some(last_time) = seen.last_updated() {
|
||||
if (last_time + Duration::minutes(BGP_RIS_REFRESH_MINUTES)) > Time::now() {
|
||||
debug!("Will not check BGP Ris Dumps until the refresh interval has passed");
|
||||
return Ok(false); // no need to update yet
|
||||
}
|
||||
}
|
||||
let announcements = loader.download_updates().await?;
|
||||
if seen.equivalent(&announcements) {
|
||||
info!("BGP Ris Dumps unchanged");
|
||||
Ok(false)
|
||||
} else {
|
||||
info!(
|
||||
"Found {} announcements based on BGP Ris Dumps",
|
||||
announcements.len()
|
||||
);
|
||||
seen.update(announcements);
|
||||
Ok(true)
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn analyse(&self, roas: &[RoaDefinition], scope: &ResourceSet) -> BgpAnalysisReport {
|
||||
let seen = self.seen.read().unwrap();
|
||||
let mut entries = vec![];
|
||||
|
||||
if seen.last_updated().is_none() {
|
||||
// nothing to analyse, just push all ROAs as 'no announcement info'
|
||||
for roa in roas {
|
||||
entries.push(BgpAnalysisEntry::roa_no_announcement_info(*roa));
|
||||
}
|
||||
} else {
|
||||
let roa_tree = make_roa_tree(roas);
|
||||
|
||||
let (v4_scope, v6_scope) = IpRange::for_resource_set(&scope);
|
||||
|
||||
let mut scoped_announcements = vec![];
|
||||
|
||||
for block in v4_scope.into_iter() {
|
||||
scoped_announcements.append(&mut seen.contained_by(block));
|
||||
}
|
||||
|
||||
for block in v6_scope.into_iter() {
|
||||
scoped_announcements.append(&mut seen.contained_by(block));
|
||||
}
|
||||
|
||||
let validated: Vec<ValidatedAnnouncement> = scoped_announcements
|
||||
.into_iter()
|
||||
.map(|a| a.validate(&roa_tree))
|
||||
.collect();
|
||||
|
||||
// Check all ROAs.. and report ROA state in relation to validated announcements
|
||||
let validated_tree = make_validated_announcement_tree(validated.as_slice());
|
||||
for roa in roas {
|
||||
let covered = validated_tree.matching_or_more_specific(&roa.prefix());
|
||||
if covered.is_empty() {
|
||||
entries.push(BgpAnalysisEntry::roa_stale(*roa))
|
||||
} else {
|
||||
let allows: Vec<Announcement> = covered
|
||||
.iter()
|
||||
.filter(|va| {
|
||||
// VALID announcements under THIS ROA
|
||||
// Already covered so it's under this ROA's prefix
|
||||
// ASN must match
|
||||
// Prefix length must be allowed under this ROA (it could be allowed by another ROA and therefore valid)
|
||||
va.validity() == AnnouncementValidity::Valid
|
||||
&& va.announcement().prefix().addr_len()
|
||||
<= roa.effective_max_length()
|
||||
&& va.announcement().asn() == &roa.asn()
|
||||
})
|
||||
.map(|va| va.announcement())
|
||||
.collect();
|
||||
|
||||
let disallows: Vec<Announcement> = covered
|
||||
.iter()
|
||||
.filter(|va| {
|
||||
let validity = va.validity();
|
||||
validity == AnnouncementValidity::InvalidLength
|
||||
|| validity == AnnouncementValidity::InvalidAsn
|
||||
})
|
||||
.map(|va| va.announcement())
|
||||
.collect();
|
||||
|
||||
entries.push(BgpAnalysisEntry::roa_seen(*roa, allows, disallows))
|
||||
}
|
||||
}
|
||||
|
||||
// Loop over all validated announcements and report
|
||||
for v in validated.into_iter() {
|
||||
let (announcement, validity, allowed_by, invalidating_roas) = v.unpack();
|
||||
match validity {
|
||||
AnnouncementValidity::Valid => {
|
||||
entries.push(BgpAnalysisEntry::announcement_valid(
|
||||
announcement,
|
||||
allowed_by.unwrap(), // always set for valid announcements
|
||||
))
|
||||
}
|
||||
AnnouncementValidity::InvalidLength => {
|
||||
entries.push(BgpAnalysisEntry::announcement_invalid_length(
|
||||
announcement,
|
||||
invalidating_roas,
|
||||
));
|
||||
}
|
||||
AnnouncementValidity::InvalidAsn => {
|
||||
entries.push(BgpAnalysisEntry::announcement_invalid_asn(
|
||||
announcement,
|
||||
invalidating_roas,
|
||||
));
|
||||
}
|
||||
AnnouncementValidity::NotFound => {
|
||||
entries.push(BgpAnalysisEntry::announcement_not_found(announcement));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BgpAnalysisReport::new(entries)
|
||||
}
|
||||
|
||||
fn test_announcements() -> Vec<Announcement> {
|
||||
use crate::test::announcement;
|
||||
|
||||
let mut res = vec![];
|
||||
|
||||
res.push(announcement("10.0.0.0/22 => 64496"));
|
||||
res.push(announcement("10.0.2.0/23 => 64496"));
|
||||
res.push(announcement("10.0.0.0/24 => 64496"));
|
||||
res.push(announcement("10.0.0.0/22 => 64497"));
|
||||
res.push(announcement("10.0.0.0/21 => 64497"));
|
||||
|
||||
res.push(announcement("192.168.0.0/26 => 64497"));
|
||||
res.push(announcement("192.168.0.0/26 => 64496"));
|
||||
|
||||
res.push(announcement("2001:DB8::/32 => 64498"));
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn with_test_announcements() -> Self {
|
||||
let mut announcements = Announcements::default();
|
||||
announcements.update(Self::test_announcements());
|
||||
BgpAnalyser {
|
||||
dumploader: None,
|
||||
seen: RwLock::new(announcements),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------ Error --------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
pub enum BgpAnalyserError {
|
||||
#[display(fmt = "BGP RIS update error: {}", _0)]
|
||||
RisDump(RisDumpError),
|
||||
}
|
||||
|
||||
impl From<RisDumpError> for BgpAnalyserError {
|
||||
fn from(e: RisDumpError) -> Self {
|
||||
BgpAnalyserError::RisDump(e)
|
||||
}
|
||||
}
|
||||
|
||||
//------------ Tests --------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::commons::bgp::BgpAnalysisState;
|
||||
use crate::test::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn download_ris_dumps() {
|
||||
let bgp_risdump_v4_uri = "http://www.ris.ripe.net/dumps/riswhoisdump.IPv4.gz";
|
||||
let bgp_risdump_v6_uri = "http://www.ris.ripe.net/dumps/riswhoisdump.IPv6.gz";
|
||||
|
||||
let analyser = BgpAnalyser::new(true, bgp_risdump_v4_uri, bgp_risdump_v6_uri);
|
||||
|
||||
assert!(analyser.seen.read().unwrap().is_empty());
|
||||
assert!(analyser.seen.read().unwrap().last_updated().is_none());
|
||||
analyser.update().await.unwrap();
|
||||
assert!(!analyser.seen.read().unwrap().is_empty());
|
||||
assert!(analyser.seen.read().unwrap().last_updated().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyse_bgp() {
|
||||
let roa_authorizing = definition("10.0.0.0/22-23 => 64496");
|
||||
let roa_stale = definition("10.0.3.0/24 => 64497");
|
||||
let roa_disallowing = definition("10.0.4.0/24 => 0");
|
||||
|
||||
let resources = ResourceSet::from_strs("", "10.0.0.0/16", "").unwrap();
|
||||
|
||||
let analyser = BgpAnalyser::with_test_announcements();
|
||||
|
||||
let report = analyser.analyse(&[roa_authorizing, roa_stale, roa_disallowing], &resources);
|
||||
|
||||
let expected: BgpAnalysisReport = serde_json::from_str(include_str!(
|
||||
"../../../test-resources/bgp/expected_full_report.json"
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(report, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyse_bgp_no_announcements() {
|
||||
let roa1 = definition("10.0.0.0/23-24 => 64496");
|
||||
let roa2 = definition("10.0.3.0/24 => 64497");
|
||||
let roa3 = definition("10.0.4.0/24 => 0");
|
||||
|
||||
let resources = ResourceSet::from_strs("", "10.0.0.0/16", "").unwrap();
|
||||
|
||||
let analyser = BgpAnalyser::new(false, "", "");
|
||||
let table = analyser.analyse(&[roa1, roa2, roa3], &resources);
|
||||
let table_entries = table.entries();
|
||||
assert_eq!(3, table_entries.len());
|
||||
|
||||
let roas_no_info: Vec<&RoaDefinition> = table_entries
|
||||
.iter()
|
||||
.filter(|e| e.state() == BgpAnalysisState::RoaNoAnnouncementInfo)
|
||||
.map(|e| e.definition())
|
||||
.collect();
|
||||
|
||||
assert_eq!(roas_no_info.as_slice(), &[&roa1, &roa2, &roa3]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::iter::FromIterator;
|
||||
use std::str::FromStr;
|
||||
|
||||
use rpki::x509::Time;
|
||||
|
||||
use crate::commons::api::{AsNumber, RoaDefinition, TypedPrefix};
|
||||
use crate::commons::bgp::{IpRange, TypedPrefixTree, TypedPrefixTreeBuilder};
|
||||
|
||||
//------------ AnnouncementTree ----------------------------------------------
|
||||
|
||||
pub type AnnouncementTree = TypedPrefixTree<Announcement>;
|
||||
|
||||
//------------ RoaTree -------------------------------------------------------
|
||||
|
||||
pub type RoaTree = TypedPrefixTree<RoaDefinition>;
|
||||
|
||||
pub fn make_roa_tree(roas: &[RoaDefinition]) -> RoaTree {
|
||||
make_tree(roas)
|
||||
}
|
||||
|
||||
pub type ValidatedAnnouncementTree = TypedPrefixTree<ValidatedAnnouncement>;
|
||||
|
||||
pub fn make_validated_announcement_tree(
|
||||
validated: &[ValidatedAnnouncement],
|
||||
) -> ValidatedAnnouncementTree {
|
||||
make_tree(validated)
|
||||
}
|
||||
|
||||
fn make_tree<V>(els: &[V]) -> TypedPrefixTree<V>
|
||||
where
|
||||
V: AsRef<TypedPrefix> + Clone,
|
||||
{
|
||||
let mut builder = TypedPrefixTreeBuilder::default();
|
||||
for el in els {
|
||||
builder.add(el.clone());
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
|
||||
//------------ Announcement --------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct Announcement {
|
||||
asn: AsNumber,
|
||||
prefix: TypedPrefix,
|
||||
}
|
||||
|
||||
impl Announcement {
|
||||
pub fn new(asn: AsNumber, prefix: TypedPrefix) -> Self {
|
||||
Announcement { asn, prefix }
|
||||
}
|
||||
|
||||
pub fn asn(&self) -> &AsNumber {
|
||||
&self.asn
|
||||
}
|
||||
|
||||
pub fn prefix(&self) -> &TypedPrefix {
|
||||
&self.prefix
|
||||
}
|
||||
|
||||
pub fn validate(&self, roas: &RoaTree) -> ValidatedAnnouncement {
|
||||
let covering = roas.matching_or_less_specific(&self.prefix);
|
||||
if covering.is_empty() {
|
||||
ValidatedAnnouncement {
|
||||
announcement: *self,
|
||||
validity: AnnouncementValidity::NotFound,
|
||||
authorizing: None,
|
||||
disallowing: vec![],
|
||||
}
|
||||
} else {
|
||||
let mut invalidating = vec![];
|
||||
let mut same_asn_found = false;
|
||||
for roa in covering {
|
||||
if roa.asn() == self.asn {
|
||||
if roa.prefix().matching_or_less_specific(&self.prefix)
|
||||
&& roa.effective_max_length() >= self.prefix.addr_len()
|
||||
{
|
||||
return ValidatedAnnouncement {
|
||||
announcement: *self,
|
||||
validity: AnnouncementValidity::Valid,
|
||||
authorizing: Some(*roa),
|
||||
disallowing: vec![],
|
||||
};
|
||||
} else {
|
||||
same_asn_found = true;
|
||||
}
|
||||
}
|
||||
invalidating.push(*roa);
|
||||
}
|
||||
|
||||
let validity = if same_asn_found {
|
||||
AnnouncementValidity::InvalidLength
|
||||
} else {
|
||||
AnnouncementValidity::InvalidAsn
|
||||
};
|
||||
|
||||
ValidatedAnnouncement {
|
||||
announcement: *self,
|
||||
validity,
|
||||
authorizing: None,
|
||||
disallowing: invalidating,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Announcement {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let as_roa =
|
||||
RoaDefinition::from_str(s).map_err(|e| format!("Can't parse: {}, Error: {}", s, e))?;
|
||||
if as_roa.max_length().is_some() {
|
||||
Err(format!(
|
||||
"Cannot parse announcement (max length not allowed): {}",
|
||||
s
|
||||
))
|
||||
} else {
|
||||
Ok(as_roa.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Announcement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} => {}", self.prefix, self.asn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Announcement {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let mut ordering = self.prefix.cmp(&other.prefix());
|
||||
if ordering == Ordering::Equal {
|
||||
ordering = self.asn.cmp(&other.asn);
|
||||
}
|
||||
ordering
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Announcement {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Announcement> for RoaDefinition {
|
||||
fn from(a: Announcement) -> Self {
|
||||
RoaDefinition::new(a.asn, a.prefix, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoaDefinition> for Announcement {
|
||||
fn from(d: RoaDefinition) -> Self {
|
||||
Announcement {
|
||||
asn: d.asn(),
|
||||
prefix: d.prefix(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<TypedPrefix> for Announcement {
|
||||
fn as_ref(&self) -> &TypedPrefix {
|
||||
&self.prefix
|
||||
}
|
||||
}
|
||||
|
||||
//------------ Announcements -------------------------------------------------
|
||||
|
||||
pub struct Announcements {
|
||||
seen: TypedPrefixTree<Announcement>,
|
||||
last_updated: Option<Time>,
|
||||
}
|
||||
|
||||
impl Announcements {
|
||||
pub fn update(&mut self, announcements: Vec<Announcement>) {
|
||||
let mut builder = TypedPrefixTreeBuilder::default();
|
||||
for a in announcements {
|
||||
builder.add(a);
|
||||
}
|
||||
let tree = builder.build();
|
||||
self.seen = tree;
|
||||
self.last_updated = Some(Time::now());
|
||||
}
|
||||
|
||||
pub fn equivalent(&self, announcements: &[Announcement]) -> bool {
|
||||
let current_set: HashSet<&Announcement> = HashSet::from_iter(self.seen.all().into_iter());
|
||||
let new_set: HashSet<&Announcement> = HashSet::from_iter(announcements.iter());
|
||||
current_set == new_set
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<&Announcement> {
|
||||
self.seen.all()
|
||||
}
|
||||
|
||||
pub fn contained_by(&self, range: impl Into<IpRange>) -> Vec<&Announcement> {
|
||||
self.seen.matching_or_more_specific(range)
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.seen.size()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.size() == 0
|
||||
}
|
||||
|
||||
pub fn last_updated(&self) -> Option<Time> {
|
||||
self.last_updated
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Announcements {
|
||||
fn default() -> Self {
|
||||
Announcements {
|
||||
seen: TypedPrefixTreeBuilder::default().build(),
|
||||
last_updated: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------ ValidatedAnnouncement -----------------------------------------
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct ValidatedAnnouncement {
|
||||
announcement: Announcement,
|
||||
validity: AnnouncementValidity,
|
||||
authorizing: Option<RoaDefinition>,
|
||||
disallowing: Vec<RoaDefinition>,
|
||||
}
|
||||
|
||||
impl ValidatedAnnouncement {
|
||||
pub fn validity(&self) -> AnnouncementValidity {
|
||||
self.validity
|
||||
}
|
||||
|
||||
pub fn announcement(&self) -> Announcement {
|
||||
self.announcement
|
||||
}
|
||||
|
||||
pub fn unpack(
|
||||
self,
|
||||
) -> (
|
||||
Announcement,
|
||||
AnnouncementValidity,
|
||||
Option<RoaDefinition>,
|
||||
Vec<RoaDefinition>,
|
||||
) {
|
||||
(
|
||||
self.announcement,
|
||||
self.validity,
|
||||
self.authorizing,
|
||||
self.disallowing,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<TypedPrefix> for ValidatedAnnouncement {
|
||||
fn as_ref(&self) -> &TypedPrefix {
|
||||
&self.announcement.prefix
|
||||
}
|
||||
}
|
||||
|
||||
//------------ AnnouncementValidity -------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub enum AnnouncementValidity {
|
||||
Valid,
|
||||
InvalidLength,
|
||||
InvalidAsn,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
//------------ Tests --------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test::*;
|
||||
|
||||
#[test]
|
||||
fn find_contained() {
|
||||
let ann_v4 = Announcement::from_str("1.0.0.0/24 => 13335").unwrap();
|
||||
let ann_v6 = Announcement::from_str("2001:4:112::/48 => 112").unwrap();
|
||||
|
||||
let mut announcements = Announcements::default();
|
||||
announcements.update(vec![ann_v4.clone(), ann_v6.clone()]);
|
||||
|
||||
let matches = announcements.contained_by(ann_v4.prefix());
|
||||
assert_eq!(1, matches.len());
|
||||
assert!(matches.contains(&&ann_v4));
|
||||
|
||||
let matches = announcements.contained_by(ann_v6.prefix());
|
||||
assert_eq!(1, matches.len());
|
||||
assert!(matches.contains(&&ann_v6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_announcement() {
|
||||
let roa_authorizing_1 = definition("10.0.0.0/23-24 => 64496");
|
||||
let roa_authorizing_2 = definition("10.0.0.0/23 => 64498");
|
||||
let roa_irrelevant = definition("10.1.0.0/23-24 => 64496");
|
||||
|
||||
let ann_v1 = announcement("10.0.0.0/24 => 64496");
|
||||
let ann_v2 = announcement("10.0.1.0/24 => 64496");
|
||||
let ann_ia = announcement("10.0.0.0/24 => 64497");
|
||||
let ann_il = announcement("10.0.1.0/24 => 64498");
|
||||
let ann_nf = announcement("10.2.0.0/24 => 64497");
|
||||
|
||||
let mut roas_builder = TypedPrefixTreeBuilder::default();
|
||||
roas_builder.add(roa_authorizing_1);
|
||||
roas_builder.add(roa_authorizing_2);
|
||||
roas_builder.add(roa_irrelevant);
|
||||
let roas = roas_builder.build();
|
||||
|
||||
fn assert_state(ann: &Announcement, roas: &RoaTree, expected: AnnouncementValidity) {
|
||||
assert_eq!(ann.validate(roas).validity, expected);
|
||||
}
|
||||
|
||||
assert_state(&ann_v1, &roas, AnnouncementValidity::Valid);
|
||||
assert_state(&ann_v2, &roas, AnnouncementValidity::Valid);
|
||||
assert_state(&ann_ia, &roas, AnnouncementValidity::InvalidAsn);
|
||||
assert_state(&ann_il, &roas, AnnouncementValidity::InvalidLength);
|
||||
assert_state(&ann_nf, &roas, AnnouncementValidity::NotFound);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
|
||||
use intervaltree::IntervalTree;
|
||||
|
||||
use crate::commons::api::{ResourceSet, TypedPrefix};
|
||||
|
||||
//------------ IpRange -----------------------------------------------------
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IpRange(Range<u128>);
|
||||
|
||||
impl IpRange {
|
||||
/// Returns the IPv4 (left) and IPv6 (right) ranges as a tuple.
|
||||
pub fn for_resource_set(set: &ResourceSet) -> (Vec<IpRange>, Vec<IpRange>) {
|
||||
let mut v4_ranges = vec![];
|
||||
let mut v6_ranges = vec![];
|
||||
if let Some(v4) = set.to_ip_resources_v4().as_blocks() {
|
||||
for block in v4.iter() {
|
||||
let min = block.min();
|
||||
let max = block.max();
|
||||
let start = min.to_v4().to_ipv6_mapped().into();
|
||||
let end = max.to_v4().to_ipv6_mapped().into();
|
||||
v4_ranges.push(IpRange(Range { start, end }))
|
||||
}
|
||||
}
|
||||
if let Some(v6) = set.to_ip_resources_v6().as_blocks() {
|
||||
for block in v6.iter() {
|
||||
let min = block.min();
|
||||
let max = block.max();
|
||||
let start = min.to_v6().into();
|
||||
let end = max.to_v6().into();
|
||||
v6_ranges.push(IpRange(Range { start, end }))
|
||||
}
|
||||
}
|
||||
(v4_ranges, v6_ranges)
|
||||
}
|
||||
|
||||
pub fn contains(&self, other: &Range<u128>) -> bool {
|
||||
self.0.start <= other.start && self.0.end >= other.end
|
||||
}
|
||||
|
||||
pub fn is_contained_by(&self, other: &Range<u128>) -> bool {
|
||||
other.start <= self.0.start && other.end >= self.0.end
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TypedPrefix> for IpRange {
|
||||
fn from(tp: &TypedPrefix) -> Self {
|
||||
match tp {
|
||||
TypedPrefix::V4(pfx) => {
|
||||
let (min, max) = pfx.as_ref().range();
|
||||
let start = min.to_v4().to_ipv6_mapped().into();
|
||||
let end = max.to_v4().to_ipv6_mapped().into();
|
||||
IpRange(Range { start, end })
|
||||
}
|
||||
TypedPrefix::V6(pfx) => {
|
||||
let (min, max) = pfx.as_ref().range();
|
||||
let start = min.to_v6().into();
|
||||
let end = max.to_v6().into();
|
||||
IpRange(Range { start, end })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------ TypedPrefixTree ---------------------------------------------
|
||||
|
||||
pub struct TypedPrefixTree<V: AsRef<TypedPrefix>> {
|
||||
tree: IntervalTree<u128, Vec<V>>,
|
||||
}
|
||||
|
||||
impl<V: AsRef<TypedPrefix>> TypedPrefixTree<V> {
|
||||
pub fn matching_or_more_specific(&self, range: impl Into<IpRange>) -> Vec<&V> {
|
||||
let range: IpRange = range.into();
|
||||
let mut res = vec![];
|
||||
for el in self.tree.query(range.0.clone()) {
|
||||
if range.contains(&el.range) {
|
||||
for v in &el.value {
|
||||
res.push(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub fn matching_or_less_specific(&self, range: impl Into<IpRange>) -> Vec<&V> {
|
||||
let range: IpRange = range.into();
|
||||
let mut res = vec![];
|
||||
for el in self.tree.query(range.0.clone()) {
|
||||
if range.is_contained_by(&el.range) {
|
||||
for v in &el.value {
|
||||
res.push(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.tree.iter().count()
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<&V> {
|
||||
self.tree
|
||||
.iter()
|
||||
.flat_map(|el| el.value.as_slice())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
//------------ TypedPrefixTreeBuilder --------------------------------------
|
||||
|
||||
pub struct TypedPrefixTreeBuilder<V: AsRef<TypedPrefix>> {
|
||||
values: HashMap<Range<u128>, Vec<V>>,
|
||||
}
|
||||
|
||||
impl<V: AsRef<TypedPrefix>> TypedPrefixTreeBuilder<V> {
|
||||
pub fn add(&mut self, value: V) {
|
||||
let range = IpRange::from(value.as_ref()).0;
|
||||
let entry = self.values.entry(range).or_insert_with(|| vec![]);
|
||||
entry.push(value);
|
||||
}
|
||||
|
||||
pub fn build(self) -> TypedPrefixTree<V> {
|
||||
let tree = self.values.into_iter().collect();
|
||||
TypedPrefixTree { tree }
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: AsRef<TypedPrefix>> Default for TypedPrefixTreeBuilder<V> {
|
||||
fn default() -> Self {
|
||||
TypedPrefixTreeBuilder {
|
||||
values: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------ Tests --------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::commons::api::ResourceSet;
|
||||
use crate::commons::bgp::Announcement;
|
||||
use std::str::FromStr;
|
||||
|
||||
fn ann(s: &str) -> Announcement {
|
||||
Announcement::from_str(s).unwrap()
|
||||
}
|
||||
|
||||
fn pfx(s: &str) -> TypedPrefix {
|
||||
TypedPrefix::from_str(s).unwrap()
|
||||
}
|
||||
|
||||
fn range_pfx(s: &str) -> IpRange {
|
||||
IpRange::from(&pfx(s))
|
||||
}
|
||||
|
||||
fn make_test_tree() -> TypedPrefixTree<Announcement> {
|
||||
let mut builder = TypedPrefixTreeBuilder::default();
|
||||
builder.add(ann("10.0.0.0/24 => 64496"));
|
||||
builder.add(ann("10.0.1.0/24 => 64496"));
|
||||
builder.add(ann("10.0.0.0/23 => 64496"));
|
||||
builder.add(ann("10.0.0.0/20 => 64496"));
|
||||
builder.add(ann("10.0.0.0/16 => 64496"));
|
||||
builder.build()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_contains() {
|
||||
let more_specific_1 = range_pfx("10.0.0.0/24");
|
||||
let more_specific_2 = range_pfx("10.0.1.0/24");
|
||||
let test_pfx = range_pfx("10.0.0.0/23");
|
||||
|
||||
assert!(test_pfx.contains(&more_specific_1.0));
|
||||
assert!(test_pfx.contains(&more_specific_2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_prefix_tree_more_specific() {
|
||||
let tree = make_test_tree();
|
||||
let search = TypedPrefix::from_str("10.0.0.0/23").unwrap();
|
||||
assert_eq!(3, tree.matching_or_more_specific(&search).len());
|
||||
|
||||
let search = TypedPrefix::from_str("10.0.2.0/24").unwrap();
|
||||
assert_eq!(0, tree.matching_or_more_specific(&search).len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_prefix_tree_less_specific() {
|
||||
let tree = make_test_tree();
|
||||
let search = TypedPrefix::from_str("10.0.0.0/23").unwrap();
|
||||
assert_eq!(3, tree.matching_or_less_specific(&search).len());
|
||||
|
||||
let search = TypedPrefix::from_str("10.0.0.0/24").unwrap();
|
||||
assert_eq!(4, tree.matching_or_less_specific(&search).len());
|
||||
|
||||
let search = TypedPrefix::from_str("10.0.0.0/16").unwrap();
|
||||
assert_eq!(1, tree.matching_or_less_specific(&search).len());
|
||||
|
||||
let search = TypedPrefix::from_str("10.0.0.0/15").unwrap();
|
||||
assert_eq!(0, tree.matching_or_less_specific(&search).len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_to_ranges() {
|
||||
let asns = "AS65000-AS65003, AS65005";
|
||||
let ipv4s = "10.0.0.0/8, 192.168.0.0";
|
||||
let ipv6s = "::1, 2001:db8::/32";
|
||||
let set = ResourceSet::from_strs(asns, ipv4s, ipv6s).unwrap();
|
||||
|
||||
let (v4_ranges, v6_ranges) = IpRange::for_resource_set(&set);
|
||||
assert_eq!(2, v4_ranges.len());
|
||||
assert_eq!(2, v6_ranges.len());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
mod analyser;
|
||||
pub use self::analyser::*;
|
||||
|
||||
mod announcements;
|
||||
pub use self::announcements::*;
|
||||
|
||||
mod iptree;
|
||||
pub use self::iptree::*;
|
||||
|
||||
mod risdumps;
|
||||
pub use self::risdumps::*;
|
||||
|
||||
mod report;
|
||||
pub use self::report::*;
|
||||
@@ -0,0 +1,558 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
use crate::commons::api::{BgpStats, RoaDefinition};
|
||||
use crate::commons::bgp::Announcement;
|
||||
|
||||
//------------ BgpAnalysisReport -------------------------------------------
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct BgpAnalysisReport(Vec<BgpAnalysisEntry>);
|
||||
|
||||
impl BgpAnalysisReport {
|
||||
pub fn new(mut roas: Vec<BgpAnalysisEntry>) -> Self {
|
||||
roas.sort();
|
||||
BgpAnalysisReport(roas)
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> &Vec<BgpAnalysisEntry> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn matching_defs(&self, state: BgpAnalysisState) -> Vec<&RoaDefinition> {
|
||||
self.matching_entries(state)
|
||||
.into_iter()
|
||||
.map(|e| &e.definition)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn matching_entries(&self, state: BgpAnalysisState) -> Vec<&BgpAnalysisEntry> {
|
||||
self.0.iter().filter(|e| e.state == state).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BgpAnalysisReport> for BgpStats {
|
||||
fn from(r: BgpAnalysisReport) -> BgpStats {
|
||||
let mut stats = BgpStats::default();
|
||||
for e in r.0.iter() {
|
||||
match e.state {
|
||||
BgpAnalysisState::AnnouncementValid => stats.increment_valid(),
|
||||
BgpAnalysisState::AnnouncementInvalidAsn => stats.increment_invalid_asn(),
|
||||
BgpAnalysisState::AnnouncementInvalidLength => stats.increment_invalid_length(),
|
||||
BgpAnalysisState::AnnouncementNotFound => stats.increment_not_found(),
|
||||
BgpAnalysisState::RoaStale => stats.increment_stale(),
|
||||
_ => {} // nothing to see, move along
|
||||
}
|
||||
}
|
||||
stats
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BgpAnalysisReport {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let entries = self.entries();
|
||||
|
||||
let mut entry_map: HashMap<BgpAnalysisState, Vec<&BgpAnalysisEntry>> = HashMap::new();
|
||||
for entry in entries.iter() {
|
||||
let state = entry.state();
|
||||
entry_map.entry(state).or_insert_with(|| vec![]);
|
||||
entry_map.get_mut(&state).unwrap().push(entry);
|
||||
}
|
||||
|
||||
if entry_map.contains_key(&BgpAnalysisState::RoaNoAnnouncementInfo) {
|
||||
write!(f, "no BGP announcements known")
|
||||
} else {
|
||||
if let Some(authorizing) = entry_map.get(&BgpAnalysisState::RoaSeen) {
|
||||
writeln!(f, "Authorizations covering announcements seen:")?;
|
||||
for roa in authorizing {
|
||||
writeln!(f)?;
|
||||
writeln!(f, "\tDefinition: {}", roa.definition)?;
|
||||
writeln!(f)?;
|
||||
writeln!(f, "\t\tAuthorizes:")?;
|
||||
for ann in roa.authorizes.iter() {
|
||||
writeln!(f, "\t\t{}", ann)?;
|
||||
}
|
||||
|
||||
if !roa.disallows.is_empty() {
|
||||
writeln!(f)?;
|
||||
writeln!(f, "\t\tDisallows:")?;
|
||||
for ann in roa.disallows.iter() {
|
||||
writeln!(f, "\t\t{}", ann)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
if let Some(stales) = entry_map.get(&BgpAnalysisState::RoaStale) {
|
||||
writeln!(
|
||||
f,
|
||||
"Authorizations for which no announcements are seen (possibly stale):"
|
||||
)?;
|
||||
writeln!(f)?;
|
||||
for roa in stales {
|
||||
writeln!(f, "\tDefinition: {}", roa.definition)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
if let Some(valids) = entry_map.get(&BgpAnalysisState::AnnouncementValid) {
|
||||
writeln!(f, "Announcements which are valid:")?;
|
||||
writeln!(f)?;
|
||||
for ann in valids {
|
||||
writeln!(f, "\tAnnouncement: {}", ann.definition)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
if let Some(invalid_asn) = entry_map.get(&BgpAnalysisState::AnnouncementInvalidAsn) {
|
||||
writeln!(f, "Announcements from an unauthorized ASN:")?;
|
||||
for ann in invalid_asn {
|
||||
writeln!(f)?;
|
||||
writeln!(f, "\tAnnouncement: {}", ann.definition)?;
|
||||
writeln!(f)?;
|
||||
writeln!(f, "\t\tDisallowed by authorization(s):")?;
|
||||
for roa in ann.disallowed_by.iter() {
|
||||
writeln!(f, "\t\t{}", roa)?;
|
||||
}
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
if let Some(invalid_length) =
|
||||
entry_map.get(&BgpAnalysisState::AnnouncementInvalidLength)
|
||||
{
|
||||
writeln!(f, "Announcements from an authorized ASN, which are too specific (not allowed by max length):")?;
|
||||
for ann in invalid_length {
|
||||
writeln!(f)?;
|
||||
writeln!(f, "\tAnnouncement: {}", ann.definition)?;
|
||||
writeln!(f)?;
|
||||
writeln!(f, "\t\tDisallowed by authorization(s):")?;
|
||||
for roa in ann.disallowed_by.iter() {
|
||||
writeln!(f, "\t\t{}", roa)?;
|
||||
}
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
if let Some(not_found) = entry_map.get(&BgpAnalysisState::AnnouncementNotFound) {
|
||||
writeln!(f, "Announcements which are 'not found' (not covered by any of your authorizations):")?;
|
||||
writeln!(f)?;
|
||||
for ann in not_found {
|
||||
writeln!(f, "\tAnnouncement: {}", ann.definition)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------ BgpAnalysisEntry --------------------------------------------
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct BgpAnalysisEntry {
|
||||
#[serde(flatten)]
|
||||
definition: RoaDefinition,
|
||||
state: BgpAnalysisState,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
allowed_by: Option<RoaDefinition>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
|
||||
disallowed_by: Vec<RoaDefinition>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
|
||||
authorizes: Vec<Announcement>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
|
||||
disallows: Vec<Announcement>,
|
||||
}
|
||||
|
||||
impl BgpAnalysisEntry {
|
||||
pub fn definition(&self) -> &RoaDefinition {
|
||||
&self.definition
|
||||
}
|
||||
|
||||
pub fn state(&self) -> BgpAnalysisState {
|
||||
self.state
|
||||
}
|
||||
|
||||
pub fn allowed_by(&self) -> Option<&RoaDefinition> {
|
||||
self.allowed_by.as_ref()
|
||||
}
|
||||
|
||||
pub fn disallowed_by(&self) -> &Vec<RoaDefinition> {
|
||||
&self.disallowed_by
|
||||
}
|
||||
|
||||
pub fn authorizes(&self) -> &Vec<Announcement> {
|
||||
&self.authorizes
|
||||
}
|
||||
|
||||
pub fn disallows(&self) -> &Vec<Announcement> {
|
||||
&self.disallows
|
||||
}
|
||||
|
||||
pub fn roa_seen(
|
||||
definition: RoaDefinition,
|
||||
mut authorizes: Vec<Announcement>,
|
||||
mut disallows: Vec<Announcement>,
|
||||
) -> Self {
|
||||
authorizes.sort();
|
||||
disallows.sort();
|
||||
BgpAnalysisEntry {
|
||||
definition,
|
||||
state: BgpAnalysisState::RoaSeen,
|
||||
allowed_by: None,
|
||||
disallowed_by: vec![],
|
||||
authorizes,
|
||||
disallows,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn roa_stale(definition: RoaDefinition) -> Self {
|
||||
BgpAnalysisEntry {
|
||||
definition,
|
||||
state: BgpAnalysisState::RoaStale,
|
||||
allowed_by: None,
|
||||
disallowed_by: vec![],
|
||||
authorizes: vec![],
|
||||
disallows: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn roa_no_announcement_info(definition: RoaDefinition) -> Self {
|
||||
BgpAnalysisEntry {
|
||||
definition,
|
||||
state: BgpAnalysisState::RoaNoAnnouncementInfo,
|
||||
allowed_by: None,
|
||||
disallowed_by: vec![],
|
||||
authorizes: vec![],
|
||||
disallows: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn announcement_valid(announcement: Announcement, allowed_by: RoaDefinition) -> Self {
|
||||
BgpAnalysisEntry {
|
||||
definition: RoaDefinition::from(announcement),
|
||||
state: BgpAnalysisState::AnnouncementValid,
|
||||
allowed_by: Some(allowed_by),
|
||||
disallowed_by: vec![],
|
||||
authorizes: vec![],
|
||||
disallows: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn announcement_invalid_asn(
|
||||
announcement: Announcement,
|
||||
mut disallowed_by: Vec<RoaDefinition>,
|
||||
) -> Self {
|
||||
disallowed_by.sort();
|
||||
BgpAnalysisEntry {
|
||||
definition: RoaDefinition::from(announcement),
|
||||
state: BgpAnalysisState::AnnouncementInvalidAsn,
|
||||
allowed_by: None,
|
||||
disallowed_by,
|
||||
authorizes: vec![],
|
||||
disallows: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn announcement_invalid_length(
|
||||
announcement: Announcement,
|
||||
mut disallowed_by: Vec<RoaDefinition>,
|
||||
) -> Self {
|
||||
disallowed_by.sort();
|
||||
BgpAnalysisEntry {
|
||||
definition: RoaDefinition::from(announcement),
|
||||
state: BgpAnalysisState::AnnouncementInvalidLength,
|
||||
allowed_by: None,
|
||||
disallowed_by,
|
||||
authorizes: vec![],
|
||||
disallows: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn announcement_not_found(announcement: Announcement) -> Self {
|
||||
BgpAnalysisEntry {
|
||||
definition: RoaDefinition::from(announcement),
|
||||
state: BgpAnalysisState::AnnouncementNotFound,
|
||||
allowed_by: None,
|
||||
disallowed_by: vec![],
|
||||
authorizes: vec![],
|
||||
disallows: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for BgpAnalysisEntry {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let mut ordering = self.state.cmp(&other.state);
|
||||
if ordering == Ordering::Equal {
|
||||
ordering = self.definition.cmp(&other.definition);
|
||||
}
|
||||
ordering
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for BgpAnalysisEntry {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
//------------ BgpAnalysisState --------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialOrd, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BgpAnalysisState {
|
||||
RoaSeen,
|
||||
RoaStale,
|
||||
AnnouncementValid,
|
||||
AnnouncementInvalidLength,
|
||||
AnnouncementInvalidAsn,
|
||||
AnnouncementNotFound,
|
||||
RoaNoAnnouncementInfo,
|
||||
}
|
||||
|
||||
//------------ AnnouncementReport ------------------------------------------
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct AnnouncementReport(Vec<AnnouncementReportEntry>);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct AnnouncementReportEntry {
|
||||
definition: RoaDefinition,
|
||||
state: AnnouncementReportState,
|
||||
}
|
||||
|
||||
impl fmt::Display for AnnouncementReportEntry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let state_str = match self.state {
|
||||
AnnouncementReportState::Valid => "announcement 'valid'",
|
||||
AnnouncementReportState::InvalidAsn => "announcement 'invalid': unauthorized asn",
|
||||
AnnouncementReportState::InvalidLength => {
|
||||
"announcement 'invalid': more specific than allowed"
|
||||
}
|
||||
AnnouncementReportState::NotFound => {
|
||||
"announcement 'not found': not covered by your ROAs"
|
||||
}
|
||||
AnnouncementReportState::Stale => {
|
||||
"ROA does not cover any known announcement (stale or backup?)"
|
||||
}
|
||||
AnnouncementReportState::NoInfo => "ROA exists, but no bgp info currently available",
|
||||
};
|
||||
write!(f, "{}\t{}", self.definition, state_str)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AnnouncementReportState {
|
||||
Valid,
|
||||
InvalidAsn,
|
||||
InvalidLength,
|
||||
NotFound,
|
||||
Stale,
|
||||
NoInfo,
|
||||
}
|
||||
|
||||
impl From<BgpAnalysisReport> for AnnouncementReport {
|
||||
fn from(table: BgpAnalysisReport) -> Self {
|
||||
let mut entries: Vec<AnnouncementReportEntry> = vec![];
|
||||
for def in table.matching_defs(BgpAnalysisState::AnnouncementValid) {
|
||||
entries.push(AnnouncementReportEntry {
|
||||
definition: *def,
|
||||
state: AnnouncementReportState::Valid,
|
||||
})
|
||||
}
|
||||
|
||||
for def in table.matching_defs(BgpAnalysisState::AnnouncementInvalidAsn) {
|
||||
entries.push(AnnouncementReportEntry {
|
||||
definition: *def,
|
||||
state: AnnouncementReportState::InvalidAsn,
|
||||
})
|
||||
}
|
||||
|
||||
for def in table.matching_defs(BgpAnalysisState::AnnouncementInvalidLength) {
|
||||
entries.push(AnnouncementReportEntry {
|
||||
definition: *def,
|
||||
state: AnnouncementReportState::InvalidLength,
|
||||
})
|
||||
}
|
||||
|
||||
for def in table.matching_defs(BgpAnalysisState::AnnouncementNotFound) {
|
||||
entries.push(AnnouncementReportEntry {
|
||||
definition: *def,
|
||||
state: AnnouncementReportState::NotFound,
|
||||
})
|
||||
}
|
||||
for def in table.matching_defs(BgpAnalysisState::RoaStale) {
|
||||
entries.push(AnnouncementReportEntry {
|
||||
definition: *def,
|
||||
state: AnnouncementReportState::Stale,
|
||||
})
|
||||
}
|
||||
for def in table.matching_defs(BgpAnalysisState::RoaNoAnnouncementInfo) {
|
||||
entries.push(AnnouncementReportEntry {
|
||||
definition: *def,
|
||||
state: AnnouncementReportState::NoInfo,
|
||||
})
|
||||
}
|
||||
AnnouncementReport(entries)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AnnouncementReport {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for e in self.0.iter() {
|
||||
writeln!(f, "{}", e)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
//------------ RoaReport ---------------------------------------------------
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct RoaReport(Vec<RoaReportEntry>);
|
||||
|
||||
impl fmt::Display for RoaReport {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for e in self.0.iter() {
|
||||
writeln!(f, "{}", e)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BgpAnalysisReport> for RoaReport {
|
||||
fn from(table: BgpAnalysisReport) -> Self {
|
||||
let mut entries: Vec<RoaReportEntry> = vec![];
|
||||
|
||||
// for def in table.matching_defs(BgpAnalysisState::RoaStale) {
|
||||
// entries.push(AnnouncementReportEntry {
|
||||
// definition: def.clone(),
|
||||
// state: AnnouncementReportState::Stale,
|
||||
// })
|
||||
// }
|
||||
|
||||
for entry in table.0 {
|
||||
match &entry.state {
|
||||
BgpAnalysisState::RoaSeen => entries.push(RoaReportEntry {
|
||||
definition: entry.definition,
|
||||
state: RoaReportEntryState::Covering,
|
||||
authorizes: entry.authorizes,
|
||||
disallows: entry.disallows,
|
||||
}),
|
||||
BgpAnalysisState::RoaStale => entries.push(RoaReportEntry {
|
||||
definition: entry.definition,
|
||||
state: RoaReportEntryState::Stale,
|
||||
authorizes: entry.authorizes,
|
||||
disallows: entry.disallows,
|
||||
}),
|
||||
BgpAnalysisState::RoaNoAnnouncementInfo => entries.push(RoaReportEntry {
|
||||
definition: entry.definition,
|
||||
state: RoaReportEntryState::NoInfo,
|
||||
authorizes: entry.authorizes,
|
||||
disallows: entry.disallows,
|
||||
}),
|
||||
BgpAnalysisState::AnnouncementNotFound => entries.push(RoaReportEntry {
|
||||
definition: entry.definition,
|
||||
state: RoaReportEntryState::NotFound,
|
||||
authorizes: entry.authorizes,
|
||||
disallows: entry.disallows,
|
||||
}),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
RoaReport(entries)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct RoaReportEntry {
|
||||
definition: RoaDefinition,
|
||||
state: RoaReportEntryState,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
|
||||
authorizes: Vec<Announcement>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")]
|
||||
disallows: Vec<Announcement>,
|
||||
}
|
||||
|
||||
impl fmt::Display for RoaReportEntry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let state_str = match self.state {
|
||||
RoaReportEntryState::Covering | RoaReportEntryState::Stale => format!(
|
||||
"roa authorizes {}, disallows {} announcements",
|
||||
self.authorizes.len(),
|
||||
self.disallows.len()
|
||||
),
|
||||
RoaReportEntryState::NotFound => {
|
||||
"announcement 'not found': not covered by your ROAs".to_string()
|
||||
}
|
||||
RoaReportEntryState::NoInfo => {
|
||||
"ROA exists, but no bgp info currently available".to_string()
|
||||
}
|
||||
};
|
||||
write!(f, "{}\t{}", self.definition, state_str)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RoaReportEntryState {
|
||||
Covering,
|
||||
Stale,
|
||||
NotFound,
|
||||
NoInfo,
|
||||
}
|
||||
|
||||
//------------ Tests --------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn print_bgp_report_full() {
|
||||
let json = include_str!("../../../test-resources/bgp/expected_full_report.json");
|
||||
let report: BgpAnalysisReport = serde_json::from_str(json).unwrap();
|
||||
|
||||
let expected = include_str!("../../../test-resources/bgp/expected_full_report.txt");
|
||||
|
||||
assert_eq!(report.to_string(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_bgp_report_announcements() {
|
||||
let json = include_str!("../../../test-resources/bgp/expected_full_report.json");
|
||||
let report: BgpAnalysisReport = serde_json::from_str(json).unwrap();
|
||||
let report: AnnouncementReport = report.into();
|
||||
|
||||
let expected_json =
|
||||
include_str!("../../../test-resources/bgp/expected_announcement_report.json");
|
||||
let expected: AnnouncementReport = serde_json::from_str(expected_json).unwrap();
|
||||
|
||||
assert_eq!(report, expected);
|
||||
|
||||
let expected_text =
|
||||
include_str!("../../../test-resources/bgp/expected_announcement_report.txt");
|
||||
assert_eq!(report.to_string(), expected_text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_bgp_report_roas() {
|
||||
let json = include_str!("../../../test-resources/bgp/expected_full_report.json");
|
||||
let report: BgpAnalysisReport = serde_json::from_str(json).unwrap();
|
||||
let report: RoaReport = report.into();
|
||||
|
||||
let expected_json = include_str!("../../../test-resources/bgp/expected_roa_report.json");
|
||||
let expected: RoaReport = serde_json::from_str(expected_json).unwrap();
|
||||
|
||||
assert_eq!(report, expected);
|
||||
|
||||
let expected_text = include_str!("../../../test-resources/bgp/expected_roa_report.txt");
|
||||
assert_eq!(report.to_string(), expected_text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
//! Support parsing announcements in RIS Dumps
|
||||
//!
|
||||
//! http://www.ris.ripe.net/dumps/riswhoisdump.IPv4.gz
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{BufRead, Read};
|
||||
use std::num::ParseIntError;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bytes::Bytes;
|
||||
use libflate::gzip::Decoder;
|
||||
|
||||
use crate::commons::api::{AsNumber, AuthorizationFmtError, TypedPrefix};
|
||||
use crate::commons::bgp::Announcement;
|
||||
|
||||
pub struct RisDumpLoader {
|
||||
bgp_risdumps_v4_uri: String,
|
||||
bgp_risdumps_v6_uri: String,
|
||||
}
|
||||
|
||||
impl RisDumpLoader {
|
||||
pub fn new(bgp_risdumps_v4_uri: &str, bgp_risdumps_v6_uri: &str) -> Self {
|
||||
RisDumpLoader {
|
||||
bgp_risdumps_v4_uri: bgp_risdumps_v4_uri.to_string(),
|
||||
bgp_risdumps_v6_uri: bgp_risdumps_v6_uri.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn download_updates(&self) -> Result<Vec<Announcement>, RisDumpError> {
|
||||
let v4_bytes: Bytes = reqwest::get(&self.bgp_risdumps_v4_uri)
|
||||
.await?
|
||||
.bytes()
|
||||
.await?;
|
||||
|
||||
let v4_bytes = Self::gunzip(v4_bytes)?;
|
||||
|
||||
let mut res = Self::parse_dump(v4_bytes.as_slice())?;
|
||||
|
||||
let v6_bytes: Bytes = reqwest::get(&self.bgp_risdumps_v6_uri)
|
||||
.await?
|
||||
.bytes()
|
||||
.await?;
|
||||
|
||||
let v6_bytes = Self::gunzip(v6_bytes)?;
|
||||
|
||||
res.append(&mut Self::parse_dump(v6_bytes.as_slice())?);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn gunzip(bytes: Bytes) -> Result<Vec<u8>, RisDumpError> {
|
||||
let mut gunzipped: Vec<u8> = vec![];
|
||||
let mut decoder = Decoder::new(bytes.as_ref()).unwrap();
|
||||
decoder.read_to_end(&mut gunzipped).unwrap();
|
||||
|
||||
Ok(gunzipped)
|
||||
}
|
||||
|
||||
fn parse_dump(bytes: &[u8]) -> Result<Vec<Announcement>, RisDumpError> {
|
||||
let mut res = vec![];
|
||||
for lres in bytes.lines() {
|
||||
let line = lres.map_err(RisDumpError::parse_error)?;
|
||||
if line.is_empty() || line.starts_with('%') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut values = line.split_whitespace();
|
||||
|
||||
let asn_str = values.next().ok_or(RisDumpError::MissingColumn)?;
|
||||
let prefix_str = values.next().ok_or(RisDumpError::MissingColumn)?;
|
||||
let peers = values.next().ok_or(RisDumpError::MissingColumn)?;
|
||||
|
||||
if u32::from_str(peers)? <= 5 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if asn_str.contains('{') {
|
||||
continue; // assets not supported (not important here either)
|
||||
}
|
||||
|
||||
let asn = AsNumber::from_str(asn_str)?;
|
||||
let prefix = TypedPrefix::from_str(prefix_str)?;
|
||||
|
||||
let ann = Announcement::new(asn, prefix);
|
||||
res.push(ann);
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
//------------ Error --------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
pub enum RisDumpError {
|
||||
#[display(fmt = "Cannot get uri: {}", _0)]
|
||||
ReqwestError(reqwest::Error),
|
||||
|
||||
#[display(fmt = "Missing column in announcements input")]
|
||||
MissingColumn,
|
||||
|
||||
#[display(fmt = "Error parsing announcements: {}", _0)]
|
||||
ParseError(String),
|
||||
}
|
||||
|
||||
impl RisDumpError {
|
||||
fn parse_error(e: impl fmt::Display) -> Self {
|
||||
RisDumpError::ParseError(format!("{}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuthorizationFmtError> for RisDumpError {
|
||||
fn from(e: AuthorizationFmtError) -> Self {
|
||||
Self::parse_error(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for RisDumpError {
|
||||
fn from(e: ParseIntError) -> Self {
|
||||
RisDumpError::parse_error(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for RisDumpError {
|
||||
fn from(e: reqwest::Error) -> RisDumpError {
|
||||
RisDumpError::ReqwestError(e)
|
||||
}
|
||||
}
|
||||
|
||||
//------------ Tests --------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn download_bgp_ris_dumps() {
|
||||
let bgp_risdump_v4_uri = "http://www.ris.ripe.net/dumps/riswhoisdump.IPv4.gz";
|
||||
let bgp_risdump_v6_uri = "http://www.ris.ripe.net/dumps/riswhoisdump.IPv6.gz";
|
||||
|
||||
let loader = RisDumpLoader::new(bgp_risdump_v4_uri, bgp_risdump_v6_uri);
|
||||
let announcements = loader.download_updates().await.unwrap();
|
||||
|
||||
assert!(!announcements.is_empty())
|
||||
}
|
||||
}
|
||||
+118
-112
@@ -179,7 +179,7 @@ pub enum Error {
|
||||
CaChildExtraResources(Handle, ChildHandle),
|
||||
|
||||
#[display(fmt = "CA '{}' does not know id certificate for child '{}'", _0, _1)]
|
||||
CaChildUnauthorised(Handle, ChildHandle),
|
||||
CaChildUnauthorized(Handle, ChildHandle),
|
||||
|
||||
#[display(
|
||||
fmt = "You can only update one aspect for child '{}' of CA '{}' at a time - i.e. either resources or ID cert",
|
||||
@@ -189,17 +189,26 @@ pub enum Error {
|
||||
CaChildUpdateOneThing(Handle, ChildHandle),
|
||||
|
||||
// RouteAuthorizations - ROAs
|
||||
#[display(fmt = "Cannot remove unknown ROA '{}' from CA '{}'", _0, _1)]
|
||||
CaAuthorisationUnknown(Handle, RouteAuthorization),
|
||||
#[display(fmt = "Cannot remove unknown ROA '{}'", _1)]
|
||||
CaAuthorizationUnknown(Handle, RouteAuthorization),
|
||||
|
||||
#[display(fmt = "Duplicate ROA '{}' for CA '{}'", _1, _0)]
|
||||
CaAuthorisationDuplicate(Handle, RouteAuthorization),
|
||||
#[display(fmt = "ROA '{}' already present", _1)]
|
||||
CaAuthorizationDuplicate(Handle, RouteAuthorization),
|
||||
|
||||
#[display(fmt = "Invalid max length in ROA: '{}' for CA '{}", _1, _0)]
|
||||
CaAuthorisationInvalidMaxlength(Handle, RouteAuthorization),
|
||||
#[display(fmt = "ROA '{}' was not added because it is redundant", _1)]
|
||||
CaAuthorizationRedundant(Handle, RouteAuthorization),
|
||||
|
||||
#[display(fmt = "Prefix in ROA '{}' not held by CA '{}'.", _1, _0)]
|
||||
CaAuthorisationNotEntitled(Handle, RouteAuthorization),
|
||||
#[display(
|
||||
fmt = "ROA '{}' was not added because it would make existing ROAs redundant",
|
||||
_1
|
||||
)]
|
||||
CaAuthorizationIncludes(Handle, RouteAuthorization),
|
||||
|
||||
#[display(fmt = "Invalid max length in ROA: '{}'", _1)]
|
||||
CaAuthorizationInvalidMaxlength(Handle, RouteAuthorization),
|
||||
|
||||
#[display(fmt = "Prefix in ROA '{}' not held by you", _1)]
|
||||
CaAuthorizationNotEntitled(Handle, RouteAuthorization),
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// Key Usage Issues
|
||||
@@ -496,8 +505,8 @@ impl Error {
|
||||
.with_ca(ca)
|
||||
.with_child(child)
|
||||
}
|
||||
Error::CaChildUnauthorised(ca, child) => {
|
||||
ErrorResponse::new("ca-child-unauthorised", &self)
|
||||
Error::CaChildUnauthorized(ca, child) => {
|
||||
ErrorResponse::new("ca-child-unauthorized", &self)
|
||||
.with_ca(ca)
|
||||
.with_child(child)
|
||||
}
|
||||
@@ -509,23 +518,35 @@ impl Error {
|
||||
}
|
||||
|
||||
// RouteAuthorizations
|
||||
Error::CaAuthorisationUnknown(ca, auth) => ErrorResponse::new("ca-roa-unknown", &self)
|
||||
Error::CaAuthorizationUnknown(ca, auth) => ErrorResponse::new("ca-roa-unknown", &self)
|
||||
.with_ca(ca)
|
||||
.with_auth(auth),
|
||||
|
||||
Error::CaAuthorisationDuplicate(ca, auth) => {
|
||||
Error::CaAuthorizationDuplicate(ca, auth) => {
|
||||
ErrorResponse::new("ca-roa-duplicate", &self)
|
||||
.with_ca(ca)
|
||||
.with_auth(auth)
|
||||
}
|
||||
|
||||
Error::CaAuthorisationInvalidMaxlength(ca, auth) => {
|
||||
Error::CaAuthorizationRedundant(ca, auth) => {
|
||||
ErrorResponse::new("ca-roa-redundant", &self)
|
||||
.with_ca(ca)
|
||||
.with_auth(auth)
|
||||
}
|
||||
|
||||
Error::CaAuthorizationIncludes(ca, auth) => {
|
||||
ErrorResponse::new("ca-roa-includes", &self)
|
||||
.with_ca(ca)
|
||||
.with_auth(auth)
|
||||
}
|
||||
|
||||
Error::CaAuthorizationInvalidMaxlength(ca, auth) => {
|
||||
ErrorResponse::new("ca-roa-invalid-max-length", &self)
|
||||
.with_ca(ca)
|
||||
.with_auth(auth)
|
||||
}
|
||||
|
||||
Error::CaAuthorisationNotEntitled(ca, auth) => {
|
||||
Error::CaAuthorizationNotEntitled(ca, auth) => {
|
||||
ErrorResponse::new("ca-roa-not-entitled", &self)
|
||||
.with_ca(ca)
|
||||
.with_auth(auth)
|
||||
@@ -611,7 +632,7 @@ mod tests {
|
||||
fn verify(expected_json: &str, e: Error) {
|
||||
let actual = e.to_error_response();
|
||||
let expected: ErrorResponse = serde_json::from_str(expected_json).unwrap();
|
||||
assert_eq!(expected, actual);
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// check that serde works too
|
||||
let serialized = serde_json::to_string(&actual).unwrap();
|
||||
@@ -625,24 +646,24 @@ mod tests {
|
||||
|
||||
let io_err = io::Error::new(io::ErrorKind::Other, "can't read file");
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/sys-io.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/sys-io.json"),
|
||||
Error::IoError(io_err),
|
||||
);
|
||||
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/sys-store.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/sys-store.json"),
|
||||
Error::AggregateStoreError(AggregateStoreError::InitError),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/sys-signer.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/sys-signer.json"),
|
||||
Error::SignerError("signer issue".to_string()),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/sys-https.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/sys-https.json"),
|
||||
Error::HttpsSetup("can't find pem file".to_string()),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/sys-http-client.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/sys-http-client.json"),
|
||||
Error::HttpClientError(httpclient::Error::Forbidden),
|
||||
);
|
||||
|
||||
@@ -654,19 +675,15 @@ mod tests {
|
||||
.err()
|
||||
.unwrap();
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/api-json.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/api-json.json"),
|
||||
Error::JsonError(json_err),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/api-unknown-method.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/api-unknown-method.json"),
|
||||
Error::ApiUnknownMethod,
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/api-unknown-resource.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/api-unknown-resource.json"),
|
||||
Error::ApiUnknownResource,
|
||||
);
|
||||
|
||||
@@ -674,7 +691,7 @@ mod tests {
|
||||
// Repository Issues
|
||||
//-----------------------------------------------------------------
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/repo-not-set.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/repo-not-set.json"),
|
||||
Error::RepoNotSet,
|
||||
);
|
||||
|
||||
@@ -682,32 +699,26 @@ mod tests {
|
||||
// Publisher Issues
|
||||
//-----------------------------------------------------------------
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/pub-unknown.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/pub-unknown.json"),
|
||||
Error::PublisherUnknown(publisher.clone()),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/pub-duplicate.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/pub-duplicate.json"),
|
||||
Error::PublisherDuplicate(publisher),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/pub-outside-jail.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/pub-outside-jail.json"),
|
||||
Error::PublisherUriOutsideBase(
|
||||
"rsync://somehost/module/folder".to_string(),
|
||||
"rsync://otherhost/module/folder".to_string(),
|
||||
),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/pub-uri-no-slash.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/pub-uri-no-slash.json"),
|
||||
Error::PublisherBaseUriNoSlash("rsync://host/module/folder".to_string()),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/pub-no-embedded-repo.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/pub-no-embedded-repo.json"),
|
||||
Error::PublisherNoEmbeddedRepo,
|
||||
);
|
||||
|
||||
@@ -715,23 +726,21 @@ mod tests {
|
||||
// RFC 8181
|
||||
//-----------------------------------------------------------------
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/rfc8181-validation.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/rfc8181-validation.json"),
|
||||
Error::Rfc8181Validation(ValidationError),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/rfc8181-decode.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/rfc8181-decode.json"),
|
||||
Error::Rfc8181Decode("could not parse CMS".to_string()),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/rfc8181-protocol-message.json"
|
||||
"../../test-resources/api/regressions/errors/rfc8181-protocol-message.json"
|
||||
),
|
||||
Error::Rfc8181MessageError(rfc8181::MessageError::InvalidVersion),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/rfc8181-delta.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/rfc8181-delta.json"),
|
||||
Error::Rfc8181Delta(PublicationDeltaError::ObjectAlreadyPresent(
|
||||
uri::Rsync::from_str("rsync://host/module/file.cer").unwrap(),
|
||||
)),
|
||||
@@ -741,187 +750,184 @@ mod tests {
|
||||
// CA Issues (label: ca-*)
|
||||
//-----------------------------------------------------------------
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ca-duplicate.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-duplicate.json"),
|
||||
Error::CaDuplicate(ca.clone()),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ca-unknown.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-unknown.json"),
|
||||
Error::CaUnknown(ca.clone()),
|
||||
);
|
||||
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ca-repo-same.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-repo-same.json"),
|
||||
Error::CaRepoInUse(ca.clone()),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ca-repo-issue.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-repo-issue.json"),
|
||||
Error::CaRepoIssue(ca.clone(), "cannot connect".to_string()),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ca-repo-response-invalid-xml.json"),
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/errors/ca-repo-response-invalid-xml.json"
|
||||
),
|
||||
Error::CaRepoResponseInvalidXml(ca.clone(), "expected some tag".to_string()),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ca-repo-response-wrong-xml.json"),
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/errors/ca-repo-response-wrong-xml.json"
|
||||
),
|
||||
Error::CaRepoResponseWrongXml(ca.clone()),
|
||||
);
|
||||
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/ca-parent-duplicate.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-parent-duplicate.json"),
|
||||
Error::CaParentDuplicate(ca.clone(), parent.clone()),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/ca-parent-unknown.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-parent-unknown.json"),
|
||||
Error::CaParentUnknown(ca.clone(), parent.clone()),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ca-parent-issue.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-parent-issue.json"),
|
||||
Error::CaParentIssue(ca.clone(), parent, "connection refused".to_string()),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ca-parent-response-invalid-xml.json"),
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/errors/ca-parent-response-invalid-xml.json"
|
||||
),
|
||||
Error::CaParentResponseInvalidXml(ca.clone(), "expected something".to_string()),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ca-parent-response-wrong-xml.json"),
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/errors/ca-parent-response-wrong-xml.json"
|
||||
),
|
||||
Error::CaParentResponseWrongXml(ca.clone()),
|
||||
);
|
||||
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/rfc6492-protocol.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/rfc6492-protocol.json"),
|
||||
Error::Rfc6492(rfc6492::Error::InvalidVersion),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/rfc6492-invalid-csr.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/rfc6492-invalid-csr.json"),
|
||||
Error::Rfc6492InvalidCsrSent("invalid signature".to_string()),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/rfc6492-invalid-signature.json"
|
||||
"../../test-resources/api/regressions/errors/rfc6492-invalid-signature.json"
|
||||
),
|
||||
Error::Rfc6492SignatureInvalid,
|
||||
);
|
||||
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/ca-child-duplicate.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-child-duplicate.json"),
|
||||
Error::CaChildDuplicate(ca.clone(), child.clone()),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/ca-child-unknown.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-child-unknown.json"),
|
||||
Error::CaChildUnknown(ca.clone(), child.clone()),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ca-child-resources-required.json"),
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/errors/ca-child-resources-required.json"
|
||||
),
|
||||
Error::CaChildMustHaveResources(ca.clone(), child.clone()),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/ca-child-resources-extra.json"
|
||||
"../../test-resources/api/regressions/errors/ca-child-resources-extra.json"
|
||||
),
|
||||
Error::CaChildExtraResources(ca.clone(), child.clone()),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/ca-child-unauthorised.json"
|
||||
),
|
||||
Error::CaChildUnauthorised(ca.clone(), child),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-child-unauthorized.json"),
|
||||
Error::CaChildUnauthorized(ca.clone(), child),
|
||||
);
|
||||
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ca-roa-unknown.json"),
|
||||
Error::CaAuthorisationUnknown(ca.clone(), auth),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-roa-unknown.json"),
|
||||
Error::CaAuthorizationUnknown(ca.clone(), auth),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/ca-roa-duplicate.json"
|
||||
),
|
||||
Error::CaAuthorisationDuplicate(ca.clone(), auth),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-roa-duplicate.json"),
|
||||
Error::CaAuthorizationDuplicate(ca.clone(), auth),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/ca-roa-invalid-max-length.json"
|
||||
),
|
||||
Error::CaAuthorisationInvalidMaxlength(ca.clone(), auth),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-roa-redundant.json"),
|
||||
Error::CaAuthorizationRedundant(ca.clone(), auth),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/ca-roa-not-entitled.json"
|
||||
),
|
||||
Error::CaAuthorisationNotEntitled(ca, auth),
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-roa-includes.json"),
|
||||
Error::CaAuthorizationIncludes(ca.clone(), auth),
|
||||
);
|
||||
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/key-re-use.json"),
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/errors/ca-roa-invalid-max-length.json"
|
||||
),
|
||||
Error::CaAuthorizationInvalidMaxlength(ca.clone(), auth),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/errors/ca-roa-not-entitled.json"),
|
||||
Error::CaAuthorizationNotEntitled(ca, auth),
|
||||
);
|
||||
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/errors/key-re-use.json"),
|
||||
Error::KeyUseAttemptReuse,
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/key-no-new.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/key-no-new.json"),
|
||||
Error::KeyUseNoNewKey,
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/key-no-current.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/key-no-current.json"),
|
||||
Error::KeyUseNoCurrentKey,
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/key-no-old.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/key-no-old.json"),
|
||||
Error::KeyUseNoOldKey,
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/key-no-cert.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/key-no-cert.json"),
|
||||
Error::KeyUseNoIssuedCert,
|
||||
);
|
||||
let ki = test_id_certificate()
|
||||
.subject_public_key_info()
|
||||
.key_identifier();
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/key-no-match.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/key-no-match.json"),
|
||||
Error::KeyUseNoMatch(ki),
|
||||
);
|
||||
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/rc-unknown.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/rc-unknown.json"),
|
||||
Error::ResourceClassUnknown(ResourceClassName::from("RC0")),
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/rc-resources.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/rc-resources.json"),
|
||||
Error::ResourceSetError(ResourceSetError::Mix),
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/rc-missing-resources.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/rc-missing-resources.json"),
|
||||
Error::MissingResources,
|
||||
);
|
||||
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ta-not-allowed.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/ta-not-allowed.json"),
|
||||
Error::TaNotAllowed,
|
||||
);
|
||||
verify(
|
||||
include_str!(
|
||||
"../../test-resources/api/regressions/v0_6_0/errors/ta-name-reserved.json"
|
||||
),
|
||||
include_str!("../../test-resources/api/regressions/errors/ta-name-reserved.json"),
|
||||
Error::TaNameReserved,
|
||||
);
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/ta-initialised.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/ta-initialised.json"),
|
||||
Error::TaAlreadyInitialised,
|
||||
);
|
||||
|
||||
verify(
|
||||
include_str!("../../test-resources/api/regressions/v0_6_0/errors/general-error.json"),
|
||||
include_str!("../../test-resources/api/regressions/errors/general-error.json"),
|
||||
Error::custom("some unlikely corner case"),
|
||||
);
|
||||
|
||||
@@ -929,7 +935,7 @@ mod tests {
|
||||
// for e in errs {
|
||||
// let error_response = e.to_error_response();
|
||||
//
|
||||
// let path = format!("test-resources/api/regressions/v0_6_0/errors/{}.json", error_response.label());
|
||||
// let path = format!("test-resources/api/regressions/errors/{}.json", error_response.label());
|
||||
// let path = PathBuf::from(&path);
|
||||
//
|
||||
// file::save_json(&error_response, &path).unwrap();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
@@ -11,7 +12,6 @@ use crate::commons::eventsourcing::{
|
||||
Aggregate, CommandKey, DiskKeyStore, Event, EventListener, KeyStore, KeyStoreError,
|
||||
KeyStoreVersion, StoredCommand,
|
||||
};
|
||||
use std::io;
|
||||
|
||||
const SNAPSHOT_FREQ: u64 = 5;
|
||||
|
||||
@@ -116,7 +116,7 @@ impl<A: Aggregate> DiskAggregateStore<A> {
|
||||
|
||||
if store.aggregates().is_empty() {
|
||||
store
|
||||
.set_version(&KeyStoreVersion::V0_6)
|
||||
.set_version(&KeyStoreVersion::V0_7)
|
||||
.map_err(AggregateStoreError::KeyStoreError)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ impl Default for StoredValueInfo {
|
||||
pub enum KeyStoreVersion {
|
||||
Pre0_6,
|
||||
V0_6,
|
||||
V0_7,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Common types used by the various Krill components.
|
||||
pub mod api;
|
||||
pub mod bgp;
|
||||
pub mod error;
|
||||
pub mod eventsourcing;
|
||||
pub mod remote;
|
||||
|
||||
@@ -810,7 +810,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn example_rrdp_uri() -> uri::Https {
|
||||
test::https("https://rpki.example/rrdp/notify.xml")
|
||||
test::https("https://rpki.example/rrdp/notification.xml")
|
||||
}
|
||||
|
||||
fn example_sia_base() -> uri::Rsync {
|
||||
|
||||
+10
-1
@@ -1,9 +1,16 @@
|
||||
pub const KRILL_VERSION: &str = "0.6.3";
|
||||
pub const KRILL_VERSION: &str = "0.7.0";
|
||||
pub const KRILL_SERVER_APP: &str = "Krill";
|
||||
pub const KRILL_CLIENT_APP: &str = "Krill Client";
|
||||
|
||||
pub const KRILL_DEFAULT_CONFIG_FILE: &str = "./defaults/krill.conf";
|
||||
|
||||
pub const KRILL_ENV_TEST: &str = "KRILL_TEST";
|
||||
pub const KRILL_ENV_TEST_ANN: &str = "KRILL_TEST_ANN";
|
||||
pub const KRILL_ENV_REPO_ENABLED: &str = "KRILL_REPO_ENABLED";
|
||||
pub const KRILL_ENV_USE_TA: &str = "KRILL_USE_TA";
|
||||
pub const KRILL_ENV_LOG_LEVEL: &str = "KRILL_LOG_LEVEL";
|
||||
pub const KRILL_ENV_AUTH_TOKEN: &str = "KRILL_AUTH_TOKEN";
|
||||
|
||||
pub const CASERVER_DIR: &str = "cas";
|
||||
|
||||
pub const PUBSERVER_DFLT: &str = "0";
|
||||
@@ -39,4 +46,6 @@ pub const ROA_CERTIFICATE_VALIDITY_YEARS: i32 = 1;
|
||||
pub const ROA_CERTIFICATE_REISSUE_WEEKS: i64 = 4;
|
||||
pub const ID_CERTIFICATE_VALIDITY_YEARS: i32 = 15;
|
||||
|
||||
pub const BGP_RIS_REFRESH_MINUTES: i64 = 60;
|
||||
|
||||
pub const HTTTP_CLIENT_TIMEOUT_SECS: u64 = 120;
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use crate::commons::api::Token;
|
||||
|
||||
//------------ Authorizer ----------------------------------------------------
|
||||
|
||||
/// This type is responsible for checking authorisations when the API is
|
||||
/// This type is responsible for checking authorizations when the API is
|
||||
/// accessed.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Authorizer {
|
||||
|
||||
+16
-10
@@ -29,7 +29,9 @@ use crate::commons::remote::rfc6492;
|
||||
use crate::commons::remote::rfc8183;
|
||||
use crate::commons::remote::sigmsg::SignedMessage;
|
||||
use crate::commons::KrillResult;
|
||||
use crate::constants::{CHILD_CERTIFICATE_REISSUE_WEEKS, CHILD_CERTIFICATE_VALIDITY_YEARS};
|
||||
use crate::constants::{
|
||||
CHILD_CERTIFICATE_REISSUE_WEEKS, CHILD_CERTIFICATE_VALIDITY_YEARS, KRILL_ENV_TEST,
|
||||
};
|
||||
use crate::daemon::ca::events::ChildCertificateUpdates;
|
||||
use crate::daemon::ca::rc::PublishMode;
|
||||
use crate::daemon::ca::signing::CsrInfo;
|
||||
@@ -538,7 +540,7 @@ impl<S: Signer> CertAuth<S> {
|
||||
|
||||
let child_cert = child
|
||||
.id_cert()
|
||||
.ok_or_else(|| Error::CaChildUnauthorised(self.handle.clone(), child_handle.clone()))?;
|
||||
.ok_or_else(|| Error::CaChildUnauthorized(self.handle.clone(), child_handle.clone()))?;
|
||||
|
||||
msg.validate(child_cert)
|
||||
.map_err(|_| Error::Rfc6492SignatureInvalid)?;
|
||||
@@ -687,7 +689,7 @@ impl<S: Signer> CertAuth<S> {
|
||||
|
||||
/// Certifies a child, unless:
|
||||
/// = the child is unknown,
|
||||
/// = the child is not authorised,
|
||||
/// = the child is not authorized,
|
||||
/// = the csr is invalid,
|
||||
/// = the limit exceeds the child allocation,
|
||||
/// = the signer throws up..
|
||||
@@ -703,7 +705,7 @@ impl<S: Signer> CertAuth<S> {
|
||||
let (rcn, limit, csr) = request.unpack();
|
||||
let csr_info = CsrInfo::try_from(&csr)?;
|
||||
|
||||
if csr_info.contains_localhost() && env::var("KRILL_TEST").is_err() {
|
||||
if csr_info.contains_localhost() && env::var(KRILL_ENV_TEST).is_err() {
|
||||
return Err(Error::invalid_csr(
|
||||
"Cannot use localhost in certificate requests unless server uses TEST mode.",
|
||||
));
|
||||
@@ -1480,7 +1482,7 @@ impl<S: Signer> CertAuth<S> {
|
||||
///
|
||||
impl<S: Signer> CertAuth<S> {
|
||||
/// Updates the route authorizations for this CA, and update ROAs. Will return
|
||||
/// an error in case authorisations are added for which this CA does not hold
|
||||
/// an error in case authorizations are added for which this CA does not hold
|
||||
/// the prefix.
|
||||
fn route_authorizations_update(
|
||||
&self,
|
||||
@@ -1502,17 +1504,21 @@ impl<S: Signer> CertAuth<S> {
|
||||
|
||||
for auth in added {
|
||||
if !auth.max_length_valid() {
|
||||
return Err(Error::CaAuthorisationInvalidMaxlength(
|
||||
return Err(Error::CaAuthorizationInvalidMaxlength(
|
||||
self.handle.clone(),
|
||||
auth,
|
||||
));
|
||||
}
|
||||
if current_auths.contains(&auth) {
|
||||
return Err(Error::CaAuthorisationDuplicate(self.handle.clone(), auth));
|
||||
return Err(Error::CaAuthorizationDuplicate(self.handle.clone(), auth));
|
||||
} else if !all_resources.contains(&auth.prefix().into()) {
|
||||
return Err(Error::CaAuthorisationNotEntitled(self.handle.clone(), auth));
|
||||
return Err(Error::CaAuthorizationNotEntitled(self.handle.clone(), auth));
|
||||
} else if current_auths.iter().any(|a| a.includes(auth.as_ref())) {
|
||||
return Err(Error::CaAuthorizationRedundant(self.handle.clone(), auth));
|
||||
} else if current_auths.iter().any(|a| auth.includes(a.as_ref())) {
|
||||
return Err(Error::CaAuthorizationIncludes(self.handle.clone(), auth));
|
||||
} else {
|
||||
current_auths.insert(auth);
|
||||
current_auths.insert(auth.explicit_length());
|
||||
res.push(StoredEvent::new(
|
||||
self.handle(),
|
||||
version,
|
||||
@@ -1532,7 +1538,7 @@ impl<S: Signer> CertAuth<S> {
|
||||
));
|
||||
version += 1;
|
||||
} else {
|
||||
return Err(Error::CaAuthorisationUnknown(self.handle.clone(), auth));
|
||||
return Err(Error::CaAuthorizationUnknown(self.handle.clone(), auth));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-1
@@ -29,6 +29,10 @@ impl RouteAuthorization {
|
||||
pub fn new(definition: RoaDefinition) -> Self {
|
||||
RouteAuthorization(definition)
|
||||
}
|
||||
|
||||
pub fn explicit_length(self) -> Self {
|
||||
RouteAuthorization(self.0.explicit_max_length())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<RoaDefinition> for RouteAuthorization {
|
||||
@@ -300,7 +304,13 @@ impl Roas {
|
||||
let signing_key = certified_key.key_id();
|
||||
|
||||
let mut roa_builder = RoaBuilder::new(auth.asn().into());
|
||||
roa_builder.push_addr(prefix.ip_addr(), prefix.addr_len(), auth.max_length());
|
||||
|
||||
if auth.effective_max_length() > auth.prefix().prefix().addr_len() {
|
||||
roa_builder.push_addr(prefix.ip_addr(), prefix.addr_len(), auth.max_length());
|
||||
} else {
|
||||
roa_builder.push_addr(prefix.ip_addr(), prefix.addr_len(), None);
|
||||
}
|
||||
|
||||
let mut object_builder = SignedObjectBuilder::new(
|
||||
Serial::random(signer).map_err(ca::Error::signer)?,
|
||||
SignSupport::sign_validity_years(ROA_CERTIFICATE_VALIDITY_YEARS),
|
||||
|
||||
+44
-15
@@ -33,13 +33,13 @@ impl ConfigDefaults {
|
||||
3000
|
||||
}
|
||||
fn test_mode() -> bool {
|
||||
env::var("KRILL_TEST").is_ok()
|
||||
env::var(KRILL_ENV_TEST).is_ok()
|
||||
}
|
||||
fn repo_enabled() -> bool {
|
||||
env::var("KRILL_REPO_ENABLED").is_ok()
|
||||
env::var(KRILL_ENV_REPO_ENABLED).is_ok()
|
||||
}
|
||||
fn use_ta() -> bool {
|
||||
env::var("KRILL_USE_TA").is_ok()
|
||||
env::var(KRILL_ENV_USE_TA).is_ok()
|
||||
}
|
||||
fn https_mode() -> HttpsMode {
|
||||
HttpsMode::Generate
|
||||
@@ -54,7 +54,7 @@ impl ConfigDefaults {
|
||||
"https://localhost:3000/".to_string()
|
||||
}
|
||||
fn log_level() -> LevelFilter {
|
||||
match env::var("KRILL_LOG_LEVEL") {
|
||||
match env::var(KRILL_ENV_LOG_LEVEL) {
|
||||
Ok(level) => LevelFilter::from_str(&level).unwrap(),
|
||||
_ => LevelFilter::Info,
|
||||
}
|
||||
@@ -70,7 +70,7 @@ impl ConfigDefaults {
|
||||
}
|
||||
|
||||
fn auth_token() -> Token {
|
||||
match env::var("KRILL_AUTH_TOKEN") {
|
||||
match env::var(KRILL_ENV_AUTH_TOKEN) {
|
||||
Ok(token) => Token::from(token),
|
||||
Err(_) => {
|
||||
eprintln!("You MUST provide a value for the master API key, either by setting \"auth_token\" in the config file, or by setting the KRILL_AUTH_TOKEN environment variable.");
|
||||
@@ -93,6 +93,18 @@ impl ConfigDefaults {
|
||||
fn post_limit_rfc6492() -> u64 {
|
||||
1024 * 1024 // 1MB (for ref. the NIC br cert is about 200kB)
|
||||
}
|
||||
|
||||
fn bgp_risdumps_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn bgp_risdumps_v4_uri() -> String {
|
||||
"http://www.ris.ripe.net/dumps/riswhoisdump.IPv4.gz".to_string()
|
||||
}
|
||||
|
||||
fn bgp_risdumps_v6_uri() -> String {
|
||||
"http://www.ris.ripe.net/dumps/riswhoisdump.IPv6.gz".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
//------------ Config --------------------------------------------------------
|
||||
@@ -166,6 +178,14 @@ pub struct Config {
|
||||
#[serde(default = "ConfigDefaults::post_limit_rfc6492")]
|
||||
pub post_limit_rfc6492: u64,
|
||||
pub rfc6492_log_dir: Option<PathBuf>,
|
||||
|
||||
// RIS BGP
|
||||
#[serde(default = "ConfigDefaults::bgp_risdumps_enabled")]
|
||||
pub bgp_risdumps_enabled: bool,
|
||||
#[serde(default = "ConfigDefaults::bgp_risdumps_v4_uri")]
|
||||
pub bgp_risdumps_v4_uri: String,
|
||||
#[serde(default = "ConfigDefaults::bgp_risdumps_v6_uri")]
|
||||
pub bgp_risdumps_v6_uri: String,
|
||||
}
|
||||
|
||||
/// # Accessors
|
||||
@@ -258,6 +278,10 @@ impl Config {
|
||||
Some(dir)
|
||||
};
|
||||
|
||||
let bgp_risdumps_enabled = false;
|
||||
let bgp_risdumps_v4_uri = ConfigDefaults::bgp_risdumps_v4_uri();
|
||||
let bgp_risdumps_v6_uri = ConfigDefaults::bgp_risdumps_v6_uri();
|
||||
|
||||
Config {
|
||||
ip,
|
||||
port,
|
||||
@@ -281,6 +305,9 @@ impl Config {
|
||||
rfc8181_log_dir,
|
||||
post_limit_rfc6492,
|
||||
rfc6492_log_dir,
|
||||
bgp_risdumps_enabled,
|
||||
bgp_risdumps_v4_uri,
|
||||
bgp_risdumps_v6_uri,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,13 +335,7 @@ impl Config {
|
||||
.short("c")
|
||||
.long("config")
|
||||
.value_name("FILE")
|
||||
.help(
|
||||
"Specify non-default config file. If no file is \
|
||||
specified './daemon/defaults/krill.conf' will be used to \
|
||||
determine default values for all settings. Note that you \
|
||||
can use any of the following options to override any of \
|
||||
these values..",
|
||||
)
|
||||
.help("Override the path to the config file (default: './defaults/krill.conf')")
|
||||
.required(false),
|
||||
)
|
||||
.get_matches();
|
||||
@@ -370,7 +391,7 @@ impl Config {
|
||||
// Set KRILL_TEST env var so that it can easily be accessed without the need to pass
|
||||
// this setting down all over the application. Used by CertAuth in particular to allow
|
||||
// the use of 'localhost' in Certificate Sign Requests in test mode only.
|
||||
env::set_var("KRILL_TEST", "1");
|
||||
env::set_var(KRILL_ENV_TEST, "1");
|
||||
}
|
||||
|
||||
if !self.test_mode
|
||||
@@ -405,6 +426,14 @@ impl Config {
|
||||
|
||||
if !self.service_uri.ends_with('/') {
|
||||
return Err(ConfigError::other("service URI must end with '/'"));
|
||||
} else {
|
||||
uri::Https::from_str(&self.service_uri).map_err(|_| {
|
||||
ConfigError::Other(format!("Invalid service uri: {}", self.service_uri))
|
||||
})?;
|
||||
|
||||
if self.service_uri.as_str().matches('/').count() != 3 {
|
||||
return Err(ConfigError::other("Service URI MUST specify a host name only, e.g. https://rpki.example.com:3000/"));
|
||||
}
|
||||
}
|
||||
|
||||
if !self.rrdp_service_uri().to_string().ends_with('/') {
|
||||
@@ -650,8 +679,8 @@ mod tests {
|
||||
// Config for auth token is required! If there is nothing in the conf
|
||||
// file, then an environment variable must be set.
|
||||
use std::env;
|
||||
env::set_var("KRILL_AUTH_TOKEN", "secret");
|
||||
env::set_var("KRILL_TEST", "1");
|
||||
env::set_var(KRILL_ENV_AUTH_TOKEN, "secret");
|
||||
env::set_var(KRILL_ENV_TEST, "1");
|
||||
|
||||
let c = Config::read_config("./defaults/krill.conf").unwrap();
|
||||
let expected_socket_addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
|
||||
|
||||
+24
-7
@@ -70,6 +70,7 @@ impl AsRef<str> for ContentType {
|
||||
struct Response {
|
||||
status: StatusCode,
|
||||
content_type: ContentType,
|
||||
max_age: Option<usize>,
|
||||
body: Vec<u8>,
|
||||
}
|
||||
|
||||
@@ -78,18 +79,22 @@ impl Response {
|
||||
Response {
|
||||
status,
|
||||
content_type: ContentType::Text,
|
||||
max_age: None,
|
||||
body: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize(self) -> HttpResponse {
|
||||
HttpResponse(
|
||||
hyper::Response::builder()
|
||||
.status(self.status)
|
||||
.header("Content-Type", self.content_type.as_ref())
|
||||
.body(self.body.into())
|
||||
.unwrap(),
|
||||
)
|
||||
let mut builder = hyper::Response::builder()
|
||||
.status(self.status)
|
||||
.header("Content-Type", self.content_type.as_ref());
|
||||
if let Some(max_age) = self.max_age {
|
||||
builder = builder.header("Cache-Control", &format!("max-age={}", max_age));
|
||||
}
|
||||
|
||||
let response = builder.body(self.body.into()).unwrap();
|
||||
|
||||
HttpResponse(response)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +123,7 @@ impl HttpResponse {
|
||||
Response {
|
||||
status: StatusCode::OK,
|
||||
content_type,
|
||||
max_age: None,
|
||||
body,
|
||||
}
|
||||
.finalize()
|
||||
@@ -142,6 +148,16 @@ impl HttpResponse {
|
||||
Self::ok_response(ContentType::Xml, body)
|
||||
}
|
||||
|
||||
pub fn xml_with_cache(body: Vec<u8>, seconds: usize) -> Self {
|
||||
Response {
|
||||
status: StatusCode::OK,
|
||||
content_type: ContentType::Xml,
|
||||
max_age: Some(seconds),
|
||||
body,
|
||||
}
|
||||
.finalize()
|
||||
}
|
||||
|
||||
pub fn rfc8181(body: Vec<u8>) -> Self {
|
||||
Self::ok_response(ContentType::Rfc8181, body)
|
||||
}
|
||||
@@ -190,6 +206,7 @@ impl HttpResponse {
|
||||
Response {
|
||||
status,
|
||||
content_type: ContentType::Json,
|
||||
max_age: None,
|
||||
body: body.into_bytes(),
|
||||
}
|
||||
.finalize()
|
||||
|
||||
+122
-11
@@ -1,5 +1,6 @@
|
||||
//! Hyper based HTTP server for Krill.
|
||||
//!
|
||||
use std::collections::HashMap;
|
||||
use std::convert::Infallible;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
@@ -19,8 +20,8 @@ use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::Method;
|
||||
|
||||
use crate::commons::api::{
|
||||
ChildHandle, CommandHistoryCriteria, Handle, ParentCaContact, ParentCaReq, ParentHandle,
|
||||
PublisherList, RepositoryUpdate,
|
||||
BgpStats, ChildHandle, CommandHistoryCriteria, Handle, ParentCaContact, ParentCaReq,
|
||||
ParentHandle, PublisherList, RepositoryUpdate,
|
||||
};
|
||||
use crate::commons::error::Error;
|
||||
use crate::commons::remote::rfc8183;
|
||||
@@ -28,7 +29,7 @@ use crate::daemon::config::Config;
|
||||
use crate::daemon::http::statics::statics;
|
||||
use crate::daemon::http::{tls, tls_keys, HttpResponse, Request, RequestPath, RoutingResult};
|
||||
use crate::daemon::krillserver::KrillServer;
|
||||
use crate::upgrades::upgrade;
|
||||
use crate::upgrades::{post_start_upgrade, pre_start_upgrade};
|
||||
|
||||
//------------ State -----------------------------------------------------
|
||||
|
||||
@@ -36,13 +37,16 @@ pub type State = Arc<RwLock<KrillServer>>;
|
||||
|
||||
pub async fn start(config: Config) -> Result<(), Error> {
|
||||
// Call upgrade, this will only do actual work if needed.
|
||||
upgrade(&config.data_dir).map_err(|_| Error::custom("Could not upgrade Krill, check logs!"))?;
|
||||
pre_start_upgrade(&config.data_dir)
|
||||
.map_err(|_| Error::custom("Could not upgrade Krill, check logs!"))?;
|
||||
|
||||
// Create the server, this will create the necessary data sub-directories if needed
|
||||
let state = {
|
||||
let krill = KrillServer::build(&config)?;
|
||||
Arc::new(RwLock::new(krill))
|
||||
};
|
||||
let krill = KrillServer::build(&config)?;
|
||||
|
||||
post_start_upgrade(&config.data_dir, &krill)
|
||||
.map_err(|_| Error::custom("Could not upgrade Krill, check logs!"))?;
|
||||
|
||||
let state = Arc::new(RwLock::new(krill));
|
||||
|
||||
let service = make_service_fn(move |_| {
|
||||
let state = state.clone();
|
||||
@@ -172,6 +176,28 @@ pub async fn metrics(req: Request) -> RoutingResult {
|
||||
let server = req.state();
|
||||
let server = server.read().await;
|
||||
|
||||
struct AllBgpStats {
|
||||
announcements_valid: HashMap<Handle, usize>,
|
||||
announcements_invalid_asn: HashMap<Handle, usize>,
|
||||
announcements_invalid_length: HashMap<Handle, usize>,
|
||||
announcements_not_found: HashMap<Handle, usize>,
|
||||
roas_stale: HashMap<Handle, usize>,
|
||||
}
|
||||
|
||||
impl AllBgpStats {
|
||||
fn add_ca(&mut self, ca: &Handle, stats: &BgpStats) {
|
||||
self.announcements_valid
|
||||
.insert(ca.clone(), stats.announcements_valid);
|
||||
self.announcements_invalid_asn
|
||||
.insert(ca.clone(), stats.announcements_invalid_asn);
|
||||
self.announcements_invalid_length
|
||||
.insert(ca.clone(), stats.announcements_invalid_length);
|
||||
self.announcements_not_found
|
||||
.insert(ca.clone(), stats.announcements_not_found);
|
||||
self.roas_stale.insert(ca.clone(), stats.roas_stale);
|
||||
}
|
||||
}
|
||||
|
||||
let mut res = String::new();
|
||||
|
||||
let info = server.server_info();
|
||||
@@ -274,6 +300,76 @@ pub async fn metrics(req: Request) -> RoutingResult {
|
||||
));
|
||||
}
|
||||
|
||||
// Aggregate ROA vs BGP stats per status
|
||||
let mut all_bgp_stats = AllBgpStats {
|
||||
announcements_valid: HashMap::new(),
|
||||
announcements_invalid_asn: HashMap::new(),
|
||||
announcements_invalid_length: HashMap::new(),
|
||||
announcements_not_found: HashMap::new(),
|
||||
roas_stale: HashMap::new(),
|
||||
};
|
||||
for (ca, status) in cas_status.iter() {
|
||||
all_bgp_stats.add_ca(ca, status.bgp_stats());
|
||||
}
|
||||
|
||||
res.push_str("\n");
|
||||
res.push_str("# HELP krill_cas_bgp_announcements_valid number of announcements seen for CA resources with RPKI state VALID\n");
|
||||
res.push_str("# TYPE krill_cas_bgp_announcements_valid gauge\n");
|
||||
for (ca, nr) in all_bgp_stats.announcements_valid.iter() {
|
||||
res.push_str(&format!(
|
||||
"krill_cas_bgp_announcements_valid{{ca=\"{}\"}} {}\n",
|
||||
ca, nr
|
||||
));
|
||||
}
|
||||
|
||||
res.push_str("\n");
|
||||
res.push_str(
|
||||
"# HELP krill_cas_bgp_announcements_invalid_asn number of announcements seen for CA resources with RPKI state INVALID (ASN mismatch)\n",
|
||||
);
|
||||
res.push_str("# TYPE krill_cas_bgp_announcements_invalid_asn gauge\n");
|
||||
for (ca, nr) in all_bgp_stats.announcements_invalid_asn.iter() {
|
||||
res.push_str(&format!(
|
||||
"krill_cas_bgp_announcements_invalid_asn{{ca=\"{}\"}} {}\n",
|
||||
ca, nr
|
||||
));
|
||||
}
|
||||
|
||||
res.push_str("\n");
|
||||
res.push_str(
|
||||
"# HELP krill_cas_bgp_announcements_invalid_length number of announcements seen for CA resources with RPKI state INVALID (prefix exceeds max length)\n",
|
||||
);
|
||||
res.push_str("# TYPE krill_cas_bgp_announcements_invalid_length gauge\n");
|
||||
for (ca, nr) in all_bgp_stats.announcements_invalid_length.iter() {
|
||||
res.push_str(&format!(
|
||||
"krill_cas_bgp_announcements_invalid_length{{ca=\"{}\"}} {}\n",
|
||||
ca, nr
|
||||
));
|
||||
}
|
||||
|
||||
res.push_str("\n");
|
||||
res.push_str(
|
||||
"# HELP krill_cas_bgp_announcements_not_found number of announcements seen for CA resources with RPKI state NOT FOUND (none of the CA's ROAs cover this)\n",
|
||||
);
|
||||
res.push_str("# TYPE krill_cas_bgp_announcements_not_found gauge\n");
|
||||
for (ca, nr) in all_bgp_stats.announcements_not_found.iter() {
|
||||
res.push_str(&format!(
|
||||
"krill_cas_bgp_announcements_not_found{{ca=\"{}\"}} {}\n",
|
||||
ca, nr
|
||||
));
|
||||
}
|
||||
|
||||
res.push_str("\n");
|
||||
res.push_str(
|
||||
"# HELP krill_cas_bgp_roas_stale number of ROAs for this CA for which no announcements are seen (0 may also indicate that no BGP info is available)\n",
|
||||
);
|
||||
res.push_str("# TYPE krill_cas_bgp_roas_stale gauge\n");
|
||||
for (ca, nr) in all_bgp_stats.roas_stale.iter() {
|
||||
res.push_str(&format!(
|
||||
"krill_cas_bgp_roas_stale{{ca=\"{}\"}} {}\n",
|
||||
ca, nr
|
||||
));
|
||||
}
|
||||
|
||||
Ok(HttpResponse::text(res.into_bytes()))
|
||||
} else {
|
||||
Err(req)
|
||||
@@ -490,6 +586,10 @@ async fn api_ca_routes(req: Request, path: &mut RequestPath, ca: Handle) -> Rout
|
||||
Method::POST => ca_routes_update(req, ca).await,
|
||||
_ => render_unknown_method(),
|
||||
},
|
||||
Some("analysis") => match *req.method() {
|
||||
Method::GET => ca_routes_analysis(req, path, ca).await,
|
||||
_ => render_unknown_method(),
|
||||
},
|
||||
_ => render_unknown_method(),
|
||||
}
|
||||
}
|
||||
@@ -975,8 +1075,6 @@ async fn ca_kr_activate(req: Request, handle: Handle) -> RoutingResult {
|
||||
render_empty_res(req.state().read().await.ca_keyroll_activate(handle))
|
||||
}
|
||||
|
||||
//------------ Admin: Force republish ----------------------------------------
|
||||
|
||||
/// Update the route authorizations for this CA
|
||||
async fn ca_routes_update(req: Request, handle: Handle) -> RoutingResult {
|
||||
let state = req.state().clone();
|
||||
@@ -995,6 +1093,14 @@ async fn ca_routes_show(req: Request, handle: Handle) -> RoutingResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// Show the state of ROAs vs BGP for this CA
|
||||
async fn ca_routes_analysis(req: Request, path: &mut RequestPath, handle: Handle) -> RoutingResult {
|
||||
match path.next() {
|
||||
Some("full") => render_json_res(req.state().read().await.ca_routes_bgp_analysis(&handle)),
|
||||
_ => render_unknown_method(),
|
||||
}
|
||||
}
|
||||
|
||||
//------------ Admin: Force republish ----------------------------------------
|
||||
|
||||
async fn republish_all(req: Request) -> RoutingResult {
|
||||
@@ -1027,6 +1133,11 @@ async fn rrdp(req: Request) -> RoutingResult {
|
||||
} else {
|
||||
let mut full_path: PathBuf = req.state.read().await.rrdp_base_path();
|
||||
let (_, path) = req.path.remaining().split_at(1);
|
||||
let cache_seconds = if path.ends_with("notification.xml") {
|
||||
60
|
||||
} else {
|
||||
86400
|
||||
};
|
||||
full_path.push(path);
|
||||
|
||||
match File::open(full_path) {
|
||||
@@ -1035,7 +1146,7 @@ async fn rrdp(req: Request) -> RoutingResult {
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer).unwrap();
|
||||
|
||||
Ok(HttpResponse::xml(buffer))
|
||||
Ok(HttpResponse::xml_with_cache(buffer, cache_seconds))
|
||||
}
|
||||
_ => Ok(HttpResponse::not_found()),
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use crate::commons::api::{
|
||||
RepositoryContact, RepositoryUpdate, RoaDefinition, RoaDefinitionUpdates, ServerInfo,
|
||||
TaCertDetails, UpdateChildRequest,
|
||||
};
|
||||
use crate::commons::bgp::{BgpAnalyser, BgpAnalysisReport};
|
||||
use crate::commons::error::Error;
|
||||
use crate::commons::eventsourcing::CommandKey;
|
||||
use crate::commons::remote::rfc8183;
|
||||
@@ -43,7 +44,7 @@ pub struct KrillServer {
|
||||
// The base working directory, used for various storage
|
||||
work_dir: PathBuf,
|
||||
|
||||
// Component responsible for API authorisation checks
|
||||
// Component responsible for API authorization checks
|
||||
authorizer: Authorizer,
|
||||
|
||||
// Publication server, with configured publishers
|
||||
@@ -52,6 +53,9 @@ pub struct KrillServer {
|
||||
// Handles the internal TA and/or CAs
|
||||
caserver: Arc<ca::CaServer<OpenSslSigner>>,
|
||||
|
||||
// Handles the internal TA and/or CAs
|
||||
bgp_analyser: Arc<BgpAnalyser>,
|
||||
|
||||
// Responsible for background tasks, e.g. re-publishing
|
||||
#[allow(dead_code)] // just need to keep this in scope
|
||||
scheduler: Scheduler,
|
||||
@@ -173,10 +177,17 @@ impl KrillServer {
|
||||
}
|
||||
}
|
||||
|
||||
let bgp_analyser = Arc::new(BgpAnalyser::new(
|
||||
config.bgp_risdumps_enabled,
|
||||
&config.bgp_risdumps_v4_uri,
|
||||
&config.bgp_risdumps_v6_uri,
|
||||
));
|
||||
|
||||
let scheduler = Scheduler::build(
|
||||
event_queue,
|
||||
caserver.clone(),
|
||||
pubserver.clone(),
|
||||
bgp_analyser.clone(),
|
||||
ca_refresh_rate,
|
||||
);
|
||||
|
||||
@@ -192,6 +203,7 @@ impl KrillServer {
|
||||
authorizer,
|
||||
pubserver,
|
||||
caserver,
|
||||
bgp_analyser,
|
||||
scheduler,
|
||||
started: Time::now(),
|
||||
post_limits,
|
||||
@@ -426,12 +438,17 @@ impl KrillServer {
|
||||
for ca in self.caserver.ca_list().cas() {
|
||||
// can't fail really, but to be sure
|
||||
if let Ok(ca) = self.caserver.get_ca(ca.handle()) {
|
||||
let roa_count = ca.roa_definitions().len();
|
||||
let roas = ca.roa_definitions();
|
||||
let roa_count = roas.len();
|
||||
let child_count = ca.children().count();
|
||||
|
||||
let bgp_report = self
|
||||
.bgp_analyser
|
||||
.analyse(roas.as_slice(), &ca.all_resources());
|
||||
|
||||
res.insert(
|
||||
ca.handle().clone(),
|
||||
CertAuthStats::new(roa_count, child_count),
|
||||
CertAuthStats::new(roa_count, child_count, bgp_report.into()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -667,6 +684,15 @@ impl KrillServer {
|
||||
let ca = self.caserver.get_ca(handle)?;
|
||||
Ok(ca.roa_definitions())
|
||||
}
|
||||
|
||||
pub fn ca_routes_bgp_analysis(&self, handle: &Handle) -> KrillResult<BgpAnalysisReport> {
|
||||
let ca = self.caserver.get_ca(handle)?;
|
||||
let definitions = ca.roa_definitions();
|
||||
let resources = ca.all_resources();
|
||||
Ok(self
|
||||
.bgp_analyser
|
||||
.analyse(definitions.as_slice(), &resources))
|
||||
}
|
||||
}
|
||||
|
||||
/// # Handle publication requests
|
||||
|
||||
@@ -10,6 +10,7 @@ use tokio::runtime::Runtime;
|
||||
use rpki::x509::Time;
|
||||
|
||||
use crate::commons::api::Handle;
|
||||
use crate::commons::bgp::BgpAnalyser;
|
||||
use crate::commons::util::softsigner::OpenSslSigner;
|
||||
use crate::daemon::ca::CaServer;
|
||||
use crate::daemon::mq::{EventQueueListener, QueueEvent};
|
||||
@@ -31,6 +32,10 @@ pub struct Scheduler {
|
||||
/// they are not renewed within the configured grace period.
|
||||
#[allow(dead_code)] // just need to keep this in scope
|
||||
ca_refresh_sh: ScheduleHandle,
|
||||
|
||||
/// Responsible for refreshing announcement information
|
||||
#[allow(dead_code)] // just need to keep this in scope
|
||||
announcements_refresh_sh: ScheduleHandle,
|
||||
}
|
||||
|
||||
impl Scheduler {
|
||||
@@ -38,16 +43,19 @@ impl Scheduler {
|
||||
event_queue: Arc<EventQueueListener>,
|
||||
caserver: Arc<CaServer<OpenSslSigner>>,
|
||||
pubserver: Option<Arc<PubServer>>,
|
||||
bgp_analyser: Arc<BgpAnalyser>,
|
||||
ca_refresh_rate: u32,
|
||||
) -> Self {
|
||||
let event_sh = make_event_sh(event_queue, caserver.clone(), pubserver);
|
||||
let republish_sh = make_republish_sh(caserver.clone());
|
||||
let ca_refresh_sh = make_ca_refresh_sh(caserver, ca_refresh_rate);
|
||||
let announcements_refresh_sh = make_announcements_refresh_sh(bgp_analyser);
|
||||
|
||||
Scheduler {
|
||||
event_sh,
|
||||
republish_sh,
|
||||
ca_refresh_sh,
|
||||
announcements_refresh_sh,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,3 +215,16 @@ fn make_ca_refresh_sh(caserver: Arc<CaServer<OpenSslSigner>>, refresh_rate: u32)
|
||||
});
|
||||
scheduler.watch_thread(Duration::from_millis(100))
|
||||
}
|
||||
|
||||
fn make_announcements_refresh_sh(bgp_analyser: Arc<BgpAnalyser>) -> ScheduleHandle {
|
||||
let mut scheduler = clokwerk::Scheduler::new();
|
||||
scheduler.every(1.seconds()).run(move || {
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
if let Err(e) = bgp_analyser.update().await {
|
||||
error!("Failed to update BGP announcements: {}", e)
|
||||
}
|
||||
})
|
||||
});
|
||||
scheduler.watch_thread(Duration::from_millis(100))
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ extern crate futures;
|
||||
extern crate futures_util;
|
||||
extern crate hex;
|
||||
extern crate hyper;
|
||||
extern crate intervaltree;
|
||||
extern crate libflate;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate openssl;
|
||||
|
||||
+14
-2
@@ -22,9 +22,10 @@ use crate::cli::{Error, KrillClient};
|
||||
use crate::commons::api::{
|
||||
AddChildRequest, CertAuthInfo, CertAuthInit, CertifiedKeyInfo, ChildAuthRequest, ChildHandle,
|
||||
Handle, ParentCaContact, ParentCaReq, ParentHandle, Publish, PublisherDetails, PublisherHandle,
|
||||
RepositoryUpdate, ResourceClassKeysInfo, ResourceClassName, ResourceSet, RoaDefinitionUpdates,
|
||||
UpdateChildRequest,
|
||||
RepositoryUpdate, ResourceClassKeysInfo, ResourceClassName, ResourceSet, RoaDefinition,
|
||||
RoaDefinitionUpdates, UpdateChildRequest,
|
||||
};
|
||||
use crate::commons::bgp::Announcement;
|
||||
use crate::commons::remote::rfc8183;
|
||||
use crate::commons::remote::rfc8183::ChildRequest;
|
||||
use crate::commons::util::httpclient;
|
||||
@@ -515,3 +516,14 @@ pub fn save_file(base_dir: &PathBuf, file_name: &str, content: &[u8]) {
|
||||
let mut f = File::create(full_name).unwrap();
|
||||
f.write_all(content).unwrap();
|
||||
}
|
||||
|
||||
// Support testing announcements and ROAs etc
|
||||
|
||||
pub fn announcement(s: &str) -> Announcement {
|
||||
let def = definition(s);
|
||||
Announcement::from(def)
|
||||
}
|
||||
|
||||
pub fn definition(s: &str) -> RoaDefinition {
|
||||
RoaDefinition::from_str(s).unwrap()
|
||||
}
|
||||
|
||||
+34
-7
@@ -6,10 +6,13 @@ use std::path::PathBuf;
|
||||
use std::{fmt, fs, io};
|
||||
|
||||
use crate::commons::api::Handle;
|
||||
use crate::commons::eventsourcing::{DiskKeyStore, KeyStoreError};
|
||||
use crate::commons::eventsourcing::{DiskKeyStore, KeyStore, KeyStoreError, KeyStoreVersion};
|
||||
use crate::commons::util::file;
|
||||
use crate::daemon::krillserver::KrillServer;
|
||||
use crate::upgrades::roa_cleanup_0_7_0::RoaCleanupError;
|
||||
|
||||
pub mod pre_0_6_0;
|
||||
pub mod roa_cleanup_0_7_0;
|
||||
|
||||
//------------ UpgradeError --------------------------------------------------
|
||||
|
||||
@@ -27,6 +30,9 @@ pub enum UpgradeError {
|
||||
#[display(fmt = "Cannot load: {}", _0)]
|
||||
CannotLoadAggregate(Handle),
|
||||
|
||||
#[display(fmt = "Cannot clean up redundant roas: {}", _0)]
|
||||
RoaCleanup(RoaCleanupError),
|
||||
|
||||
#[display(fmt = "{}", _0)]
|
||||
Custom(String),
|
||||
}
|
||||
@@ -59,6 +65,12 @@ impl From<io::Error> for UpgradeError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoaCleanupError> for UpgradeError {
|
||||
fn from(e: RoaCleanupError) -> Self {
|
||||
UpgradeError::RoaCleanup(e)
|
||||
}
|
||||
}
|
||||
|
||||
//------------ UpgradeStore --------------------------------------------------
|
||||
|
||||
/// Implement this for automatic upgrades to key stores
|
||||
@@ -67,12 +79,27 @@ pub trait UpgradeStore {
|
||||
fn migrate(&self, store: &DiskKeyStore) -> Result<(), UpgradeError>;
|
||||
}
|
||||
|
||||
/// Should be called when Krill starts
|
||||
pub fn upgrade(work_dir: &PathBuf) -> Result<(), UpgradeError> {
|
||||
/// Should be called when Krill starts, before the KrillServer is initiated
|
||||
pub fn pre_start_upgrade(work_dir: &PathBuf) -> Result<(), UpgradeError> {
|
||||
upgrade_pre_0_6_0_cas_commands(work_dir)?;
|
||||
upgrade_pre_0_6_0_pubd_commands(work_dir)
|
||||
}
|
||||
|
||||
/// Should be called right after the KrillServer is initiated
|
||||
pub fn post_start_upgrade(work_dir: &PathBuf, server: &KrillServer) -> Result<(), UpgradeError> {
|
||||
let version_0_7 = KeyStoreVersion::V0_7;
|
||||
let ca_store = DiskKeyStore::new(work_dir, "cas");
|
||||
let pubd_store = DiskKeyStore::new(work_dir, "pubd");
|
||||
if ca_store.get_version()? != version_0_7 {
|
||||
info!("Will clean up redundant ROAs for all CAs and update version of storage dirs");
|
||||
roa_cleanup_0_7_0::roa_cleanup(server)?;
|
||||
ca_store.set_version(&version_0_7)?;
|
||||
pubd_store.set_version(&version_0_7)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn upgrade_pre_0_6_0_cas_commands(work_dir: &PathBuf) -> Result<(), UpgradeError> {
|
||||
let pre_0_6_0_ca_commands = pre_0_6_0::UpgradeCas;
|
||||
|
||||
@@ -115,10 +142,10 @@ fn upgrade_pre_0_6_0_pubd_commands(work_dir: &PathBuf) -> Result<(), UpgradeErro
|
||||
// Prepare to do the work on the real "cas" directory
|
||||
let mut pubd_dir = work_dir.clone();
|
||||
pubd_dir.push("pubd");
|
||||
let ca_store = DiskKeyStore::new(work_dir, "pubd");
|
||||
let pubd_store = DiskKeyStore::new(work_dir, "pubd");
|
||||
|
||||
// bail out if there is nothing to do
|
||||
if !pre_0_6_0_pubd_commands.needs_migrate(&ca_store)? {
|
||||
if !pre_0_6_0_pubd_commands.needs_migrate(&pubd_store)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -128,7 +155,7 @@ fn upgrade_pre_0_6_0_pubd_commands(work_dir: &PathBuf) -> Result<(), UpgradeErro
|
||||
backup_dir.push("pubd_bk");
|
||||
file::backup_dir(&pubd_dir, &backup_dir)?;
|
||||
|
||||
if let Err(e) = pre_0_6_0_pubd_commands.migrate(&ca_store) {
|
||||
if let Err(e) = pre_0_6_0_pubd_commands.migrate(&pubd_store) {
|
||||
// If the upgrade failed, then rename the now broken directory for inspection,
|
||||
// and restore the backup directory by renaming it.
|
||||
let mut failed = work_dir.clone();
|
||||
@@ -170,7 +197,7 @@ mod tests {
|
||||
pubd_test.push("pubd");
|
||||
file::backup_dir(&pubd_source, &pubd_test).unwrap();
|
||||
|
||||
upgrade(&tmp).unwrap();
|
||||
pre_start_upgrade(&tmp).unwrap();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ impl UpgradeStore for UpgradeCas {
|
||||
fn needs_migrate(&self, store: &DiskKeyStore) -> Result<bool, UpgradeError> {
|
||||
match store.get_version() {
|
||||
Ok(version) => match version {
|
||||
KeyStoreVersion::V0_6 => Ok(false),
|
||||
KeyStoreVersion::Pre0_6 => Ok(true),
|
||||
_ => Ok(false),
|
||||
},
|
||||
Err(e) => match e {
|
||||
KeyStoreError::NotInitialised => Ok(false),
|
||||
@@ -128,8 +128,8 @@ impl UpgradeStore for UpgradePubd {
|
||||
} else {
|
||||
match store.get_version() {
|
||||
Ok(version) => match version {
|
||||
KeyStoreVersion::V0_6 => Ok(false),
|
||||
KeyStoreVersion::Pre0_6 => Ok(true),
|
||||
_ => Ok(false),
|
||||
},
|
||||
Err(e) => match e {
|
||||
KeyStoreError::NotInitialised => Ok(false),
|
||||
@@ -164,6 +164,10 @@ impl UpgradeStore for UpgradePubd {
|
||||
last_command = seq;
|
||||
seq += 1;
|
||||
}
|
||||
|
||||
if seq % 100 == 0 {
|
||||
info!(".. {} done", seq)
|
||||
}
|
||||
}
|
||||
|
||||
info!("Done migrating commands for Repository server");
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::commons::api::{RoaDefinition, RoaDefinitionUpdates};
|
||||
use crate::commons::bgp::make_roa_tree;
|
||||
use crate::daemon::krillserver::KrillServer;
|
||||
|
||||
pub fn roa_cleanup(server: &KrillServer) -> Result<(), RoaCleanupError> {
|
||||
for ca in server.cas().cas() {
|
||||
let roas = server.ca_routes_show(ca.handle())?;
|
||||
|
||||
if let Some(updates) = clean(roas) {
|
||||
server.ca_routes_update(ca.handle().clone(), updates)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clean(roas: Vec<RoaDefinition>) -> Option<RoaDefinitionUpdates> {
|
||||
let tree = make_roa_tree(roas.as_slice());
|
||||
|
||||
let mut added = HashSet::new();
|
||||
let mut removed = HashSet::new();
|
||||
|
||||
for roa in roas.into_iter() {
|
||||
// if this ROA is covered by any other ROA
|
||||
|
||||
let prefix = roa.prefix();
|
||||
let asn = roa.asn();
|
||||
|
||||
let mut should_remove = false;
|
||||
|
||||
for covering in tree.matching_or_less_specific(&prefix) {
|
||||
if covering == &roa || covering.asn() != asn {
|
||||
continue;
|
||||
}
|
||||
|
||||
if covering.prefix() == prefix && roa.max_length().is_none() {
|
||||
should_remove = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if covering.effective_max_length() > roa.effective_max_length() {
|
||||
// covering prefix is bigger and allows this
|
||||
should_remove = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if should_remove {
|
||||
removed.insert(roa);
|
||||
} else if roa.max_length().is_none() {
|
||||
// If this does not have a max length then remove
|
||||
// this one and add the equivalent with max length.
|
||||
// Note: if that equivalent would have existed this
|
||||
// roa would have been marked for removal
|
||||
let with_length = RoaDefinition::new(asn, prefix, Some(roa.effective_max_length()));
|
||||
removed.insert(roa);
|
||||
added.insert(with_length);
|
||||
}
|
||||
}
|
||||
|
||||
if !added.is_empty() || !removed.is_empty() {
|
||||
Some(RoaDefinitionUpdates::new(added, removed))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
#[display(fmt = "{}", _0)]
|
||||
pub struct RoaCleanupError(String);
|
||||
|
||||
impl From<crate::commons::error::Error> for RoaCleanupError {
|
||||
fn from(e: crate::commons::error::Error) -> Self {
|
||||
RoaCleanupError(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
//------------ Tests ---------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::test::definition;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn upgrade_roa_cleanup() {
|
||||
let roas = vec![
|
||||
definition("192.0.0.0/8-8 => 64496"), // keep
|
||||
definition("192.168.0.0/16 => 64496"), // remove there is a longer ml
|
||||
definition("192.168.0.0/16-16 => 64496"), // remove there is a longer ml
|
||||
definition("192.168.0.0/16-20 => 64496"), // keep
|
||||
definition("192.168.0.0/16-18 => 64496"), // remove there is a longer ml
|
||||
definition("192.168.0.0/18-20 => 64496"), // remove covering has longer ml
|
||||
definition("192.168.0.0/18-24 => 64496"), // keep, this is more permissive for specific bit
|
||||
definition("192.168.127.0/24-24 => 64496"), // keep, this is more specific
|
||||
definition("192.168.0.0/16-20 => 64497"), // different asn -> keep
|
||||
definition("10.0.0.0/8 => 64496"), // replace with one with max length
|
||||
definition("10.0.1.0/24 => 64498"), // remove, there is one with explicit ml
|
||||
definition("10.0.1.0/24-24 => 64498"), // keep
|
||||
];
|
||||
|
||||
let update = clean(roas).unwrap();
|
||||
|
||||
let mut expected_added = HashSet::new();
|
||||
expected_added.insert(definition("10.0.0.0/8-8 => 64496"));
|
||||
|
||||
let mut expected_removed = HashSet::new();
|
||||
expected_removed.insert(definition("10.0.0.0/8 => 64496"));
|
||||
expected_removed.insert(definition("10.0.1.0/24 => 64498"));
|
||||
expected_removed.insert(definition("192.168.0.0/16 => 64496"));
|
||||
expected_removed.insert(definition("192.168.0.0/16-16 => 64496"));
|
||||
expected_removed.insert(definition("192.168.0.0/16-18 => 64496"));
|
||||
expected_removed.insert(definition("192.168.0.0/18-20 => 64496"));
|
||||
|
||||
let expected = RoaDefinitionUpdates::new(expected_added, expected_removed);
|
||||
|
||||
assert_eq!(update, expected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"label":"ca-child-unauthorized","msg":"CA 'ca' does not know id certificate for child 'child'","args":{"ca":"ca","child":"child"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"label":"ca-roa-duplicate","msg":"ROA '192.168.0.0/16-24 => 64496' already present", "args":{"asn":"64496","ca":"ca","prefix":"192.168.0.0/16","max_length":"24"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"label":"ca-roa-includes","msg":"ROA '192.168.0.0/16-24 => 64496' was not added because it would make existing ROAs redundant", "args":{"asn":"64496","ca":"ca","prefix":"192.168.0.0/16","max_length":"24"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"label":"ca-roa-invalid-max-length","msg":"Invalid max length in ROA: '192.168.0.0/16-24 => 64496'","args":{"ca":"ca","prefix":"192.168.0.0/16","max_length":"24","asn":"64496"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"label":"ca-roa-not-entitled","msg":"Prefix in ROA '192.168.0.0/16-24 => 64496' not held by you", "args":{"asn":"64496","ca":"ca","prefix":"192.168.0.0/16","max_length":"24"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"label":"ca-roa-redundant","msg":"ROA '192.168.0.0/16-24 => 64496' was not added because it is redundant", "args":{"asn":"64496","ca":"ca","prefix":"192.168.0.0/16","max_length":"24"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"label":"ca-roa-unknown","msg":"Cannot remove unknown ROA '192.168.0.0/16-24 => 64496'","args":{"ca":"ca","max_length":"24","asn":"64496","prefix":"192.168.0.0/16"}}
|
||||
@@ -1 +0,0 @@
|
||||
{"label":"ca-child-unauthorised","msg":"CA 'ca' does not know id certificate for child 'child'","args":{"ca":"ca","child":"child"}}
|
||||
@@ -1 +0,0 @@
|
||||
{"label":"ca-roa-duplicate","msg":"Duplicate ROA '192.168.0.0/16-24 => 64496' for CA 'ca'","args":{"asn":"64496","ca":"ca","prefix":"192.168.0.0/16","max_length":"24"}}
|
||||
@@ -1 +0,0 @@
|
||||
{"label":"ca-roa-invalid-max-length","msg":"Invalid max length in ROA: '192.168.0.0/16-24 => 64496' for CA 'ca","args":{"ca":"ca","prefix":"192.168.0.0/16","max_length":"24","asn":"64496"}}
|
||||
@@ -1 +0,0 @@
|
||||
{"label":"ca-roa-not-entitled","msg":"Prefix in ROA '192.168.0.0/16-24 => 64496' not held by CA 'ca'.","args":{"asn":"64496","ca":"ca","prefix":"192.168.0.0/16","max_length":"24"}}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user