2020-11-04 17:19:46 +01:00
import rpki from "rpki-validator" ;
import fs from "fs" ;
2020-11-05 21:01:29 +01:00
import md5 from "md5" ;
2020-12-01 03:57:59 +01:00
import axiosEnrich from "./axiosEnrich" ;
import axios from "axios" ;
2020-11-04 17:19:46 +01:00
export default class RpkiUtils {
constructor ( env ) {
this . config = env . config ;
this . agent = env . agent ;
this . params = this . config . rpki || { } ;
this . clientId = env . clientId || "" ;
this . logger = env . logger ;
2020-12-01 03:57:59 +01:00
this . userAgent = ` ${ this . clientId } / ${ env . version } ` ;
2020-11-04 17:19:46 +01:00
2020-11-05 21:01:29 +01:00
const defaultMarkDataAsStaleAfterMinutes = 60 ;
2021-03-04 17:16:57 +01:00
const providers = [ "ntt" , "ripe" , "cloudflare" , "rpkiclient" , "external" , "api" ] ; // First provider is the default one
2021-03-04 18:39:03 +01:00
if ( this . params . url ) {
2021-03-04 17:16:57 +01:00
this . params . vrpProvider = "api" ;
this . params . preCacheROAs = true ;
}
2020-11-04 17:19:46 +01:00
if ( this . params . vrpFile ) {
this . params . vrpProvider = "external" ;
this . params . refreshVrpListMinutes = null ;
this . params . preCacheROAs = true ;
} else {
if ( ! this . params . vrpProvider ) {
this . params . vrpProvider = providers [ 0 ] ;
} else if ( ! providers . includes ( this . params . vrpProvider ) ) {
this . params . vrpProvider = providers [ 0 ] ;
this . logger . log ( {
level : 'error' ,
message : "The specified vrpProvider is not valid. Using default vrpProvider."
} ) ;
}
2021-03-04 17:16:57 +01:00
this . params . refreshVrpListMinutes = Math . max ( this . params . refreshVrpListMinutes || 0 , 5 ) ;
2020-11-04 17:19:46 +01:00
this . params . preCacheROAs = this . params . preCacheROAs !== false ;
}
2020-11-05 21:01:29 +01:00
if ( this . params . markDataAsStaleAfterMinutes !== undefined ) {
if ( this . params . markDataAsStaleAfterMinutes <= this . params . refreshVrpListMinutes ) {
this . logger . log ( {
level : 'error' ,
message : ` The specified markDataAsStaleAfterMinutes cannot be <= of refreshVrpListMinutes ( ${ defaultMarkDataAsStaleAfterMinutes } minutes will be used). `
} ) ;
this . params . markDataAsStaleAfterMinutes = defaultMarkDataAsStaleAfterMinutes ;
}
}
this . status = {
data : true ,
stale : false ,
provider : this . params . vrpProvider
} ;
2020-11-04 17:19:46 +01:00
this . _loadRpkiValidator ( ) ;
2020-11-05 21:01:29 +01:00
if ( this . params . markDataAsStaleAfterMinutes > 0 ) {
setInterval ( this . _markAsStale , this . params . markDataAsStaleAfterMinutes * 60 * 1000 ) ;
}
2020-11-30 19:36:15 +01:00
this . queue = [ ] ;
setInterval ( this . _validateQueue , 500 ) ; // Periodically validate prefixes-origin pairs
2020-11-04 17:19:46 +01:00
} ;
_loadRpkiValidatorFromVrpProvider = ( ) => {
if ( ! this . rpki ) {
const rpkiValidatorOptions = {
connector : this . params . vrpProvider ,
2020-12-01 03:57:59 +01:00
clientId : this . clientId ,
axios : axiosEnrich ( axios , ( ! this . params . noProxy && this . agent ) ? this . agent : null , this . userAgent )
2020-11-04 17:19:46 +01:00
} ;
2021-03-04 18:39:03 +01:00
if ( this . params . url ) {
rpkiValidatorOptions . url = this . params . url ;
2021-03-04 17:16:57 +01:00
}
2020-11-04 17:19:46 +01:00
this . rpki = new rpki ( rpkiValidatorOptions ) ;
if ( ! ! this . params . preCacheROAs ) {
2021-03-04 18:39:03 +01:00
this . _preCache ( ) ;
2020-11-04 17:19:46 +01:00
}
}
} ;
_watchVrpFile = ( vrpFile ) => {
const reload = ( ) => { // Watch the external file to refresh the list
if ( this . watchFileTimer ) {
clearTimeout ( this . watchFileTimer ) ;
}
this . watchFileTimer = setTimeout ( ( ) => {
this . logger . log ( {
level : 'info' ,
message : "VRPs reloaded due to file change."
} ) ;
this . _loadRpkiValidatorFromVrpFile ( vrpFile ) ;
} , 3000 ) ;
} ;
fs . watchFile ( vrpFile , reload ) ;
} ;
_loadRpkiValidatorFromVrpFile = ( vrpFile ) => {
if ( fs . existsSync ( vrpFile ) ) {
try {
let vrps = JSON . parse ( fs . readFileSync ( vrpFile , 'utf8' ) ) ;
if ( vrps ) {
if ( vrps . roas && vrps . roas . length ) {
vrps = vrps . roas ;
}
if ( vrps . length > 0 ) {
if ( this . rpki ) {
this . rpki . destroy ( ) ;
}
this . rpki = new rpki ( {
connector : "external" ,
clientId : this . clientId
} ) ;
this . rpki . setVRPs ( vrps ) ;
2021-03-04 18:39:03 +01:00
this . _preCache ( ) ;
2020-11-04 17:19:46 +01:00
} else {
this . logger . log ( {
level : 'error' ,
message : "The provided VRPs file is empty. Using default vrpProvider."
} ) ;
}
}
} catch ( error ) {
this . logger . log ( {
level : 'error' ,
message : "The provided VRPs file cannot be parsed. Using default vrpProvider."
} ) ;
}
} else {
this . logger . log ( {
level : 'error' ,
message : "The provided VRPs file cannot be found. Using default vrpProvider."
} ) ;
}
return this . _loadRpkiValidatorFromVrpProvider ( ) ;
} ;
_loadRpkiValidator = ( ) => {
if ( ! ! this . params . vrpFile ) {
const vrpFile = this . config . volume + this . params . vrpFile ;
this . _loadRpkiValidatorFromVrpFile ( vrpFile ) ;
this . _watchVrpFile ( vrpFile ) ;
} else {
this . _loadRpkiValidatorFromVrpProvider ( ) ;
}
} ;
_preCache = ( ) => {
if ( ! ! this . params . preCacheROAs ) {
return this . rpki
. preCache ( this . params . refreshVrpListMinutes )
2020-11-05 21:01:29 +01:00
. then ( data => {
this . status . data = true ;
this . status . stale = false ;
return data ;
} )
2020-11-04 17:19:46 +01:00
. catch ( ( ) => {
2021-03-04 18:39:03 +01:00
if ( ! this . _cannotDownloadErrorOnce ) {
this . logger . log ( {
level : 'error' ,
message : "The VRP list cannot be downloaded. The RPKI monitoring should be working anyway with one of the on-line providers."
} ) ;
}
this . _cannotDownloadErrorOnce = true ;
} )
2020-11-04 17:19:46 +01:00
} else {
2020-11-05 21:01:29 +01:00
this . status . data = true ;
this . status . stale = false ;
2020-11-04 17:19:46 +01:00
return Promise . resolve ( ) ;
}
2020-11-05 03:03:54 +01:00
} ;
2020-11-04 17:19:46 +01:00
2020-11-30 19:36:15 +01:00
_validateQueue = ( ) => {
const batch = { } ;
for ( let { message , matchedRule , callback } of this . queue ) {
const key = message . originAS . getId ( ) + "-" + message . prefix ;
batch [ key ] = batch [ key ] || [ ] ;
batch [ key ] . push ( { message , matchedRule , callback } ) ;
}
this . queue = [ ] ;
this . validateBatch ( Object
2021-03-04 17:16:57 +01:00
. values ( batch )
. map ( ( elements ) => {
const { message } = elements [ 0 ] ;
return {
prefix : message . prefix ,
origin : message . originAS
} ;
} ) )
2020-11-30 19:36:15 +01:00
. then ( results => {
for ( let result of results ) {
const key = result . origin . getId ( ) + "-" + result . prefix ;
for ( let { message , matchedRule , callback } of batch [ key ] ) {
callback ( result , message , matchedRule ) ;
}
}
} )
. catch ( error => {
this . logger . log ( {
level : 'error' ,
message : error
} ) ;
} ) ;
} ;
addToValidationQueue = ( message , matchedRule , callback ) => {
this . queue . push ( { message , matchedRule , callback } ) ;
} ;
2020-11-04 17:19:46 +01:00
validate = ( prefix , origin ) => {
2020-11-30 17:45:56 +01:00
return this . validateBatch ( [ { prefix , origin } ] )
. then ( results => results [ 0 ] ) ;
} ;
validateBatch = ( batch ) => {
2020-11-04 17:19:46 +01:00
return this . _preCache ( )
. then ( ( ) => {
2020-11-30 17:45:56 +01:00
return Promise . all ( batch
. map ( ( { prefix , origin } ) => {
const origins = [ ] . concat . apply ( [ ] , [ origin . getValue ( ) ] ) ;
return Promise
. all ( origins . map ( asn => this . rpki . validate ( prefix , asn , true ) ) ) // Validate each origin
. then ( results => {
if ( results . length === 1 ) { // Only one result = only one origin, just return
return { ... results [ 0 ] , prefix , origin } ;
} else { // Multiple origin
if ( results . every ( result => result && result . valid ) ) { // All valid
return {
valid : true ,
covering : [ ] . concat . apply ( [ ] , results . map ( i => i . covering ) ) ,
prefix ,
origin
} ;
} else if ( results . some ( result => result && ! result . valid ) ) { // At least one not valid
return {
valid : false ,
covering : [ ] . concat . apply ( [ ] , results . map ( i => i . covering ) ) ,
prefix ,
origin
} ;
} else { // return not covered
return {
valid : null ,
covering : [ ] . concat . apply ( [ ] , results . map ( i => i . covering ) ) ,
prefix ,
origin
} ;
}
}
} ) ;
} ) )
2021-03-04 18:39:03 +01:00
. catch ( error => {
this . logger . log ( {
level : 'error' ,
message : "RPKI validation failed due to:" + error
} ) ;
} )
2020-11-04 17:19:46 +01:00
} ) ;
} ;
2020-11-05 03:03:54 +01:00
getVrps = ( ) => {
2021-02-19 16:00:03 +01:00
return this . rpki . toArray ( ) ;
2020-11-05 21:01:29 +01:00
} ;
getStatus = ( ) => {
return this . status ;
} ;
_markAsStale = ( ) => {
if ( ! ! this . params . preCacheROAs ) {
const digest = md5 ( JSON . stringify ( this . getVrps ( ) ) ) ;
if ( this . oldDigest ) {
this . status . stale = this . oldDigest === digest ;
}
this . oldDigest = digest ;
}
} ;
2020-11-04 17:19:46 +01:00
}