1
0
mirror of https://github.com/nttgin/BGPalerter.git synced 2024-05-19 06:50:08 +00:00
Files
nttgin-BGPalerter/src/utils/rpkiUtils.js
2020-11-30 19:36:15 +01:00

296 lines
11 KiB
JavaScript

import rpki from "rpki-validator";
import fs from "fs";
import md5 from "md5";
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;
const defaultMarkDataAsStaleAfterMinutes = 60;
const providers = ["ntt", "ripe", "cloudflare", "external"]; // First provider is the default one
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."
});
}
this.params.refreshVrpListMinutes = Math.max(this.params.refreshVrpListMinutes || 0, 15);
this.params.preCacheROAs = this.params.preCacheROAs !== false;
}
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
};
this._loadRpkiValidator();
if (this.params.markDataAsStaleAfterMinutes > 0) {
setInterval(this._markAsStale, this.params.markDataAsStaleAfterMinutes * 60 * 1000);
}
this.queue = [];
setInterval(this._validateQueue, 500); // Periodically validate prefixes-origin pairs
};
_loadRpkiValidatorFromVrpProvider = () => {
if (!this.rpki) {
const rpkiValidatorOptions = {
connector: this.params.vrpProvider,
clientId: this.clientId
};
if (!this.params.noProxy && this.agent) {
rpkiValidatorOptions.httpsAgent = this.agent;
}
this.rpki = new rpki(rpkiValidatorOptions);
if (!!this.params.preCacheROAs) {
this._preCache()
.catch(() => {
this.logger.log({
level: 'error',
message: "One of the VRPs lists cannot be downloaded. The RPKI monitoring should be working anyway with one of the on-line providers."
});
});
}
}
};
_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);
this._preCache()
.catch(() => {
this.logger.log({
level: 'error',
message: "It was not possible to load correctly the VRPs file. Possibly there is an error in the format. The RPKI monitoring should be working anyway with one of the on-line providers."
});
});
} 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)
.then(data => {
this.status.data = true;
this.status.stale = false;
return data;
})
.catch(() => {
this.status.data = false;
this.status.stale = true;
this.logger.log({
level: 'error',
message: "The VRP list cannot be downloaded."
});
});
} else {
this.status.data = true;
this.status.stale = false;
return Promise.resolve();
}
};
_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
.values(batch)
.map((elements) => {
const { message } = elements[0];
return {
prefix: message.prefix,
origin: message.originAS
};
}))
.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 });
};
validate = (prefix, origin) => {
return this.validateBatch([{ prefix, origin }])
.then(results => results[0]);
};
validateBatch = (batch) => {
return this._preCache()
.then(() => {
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
};
}
}
});
}))
});
};
getVrps = () => {
return [].concat.apply([],[...this.rpki.getRadixTrie().v4.values(), ...this.rpki.getRadixTrie().v6.values()]);
};
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;
}
};
}