1
0
mirror of https://github.com/nttgin/BGPalerter.git synced 2024-05-19 06:50:08 +00:00

Merge branch 'dev' into debian-ci

This commit is contained in:
Massimo Candela
2023-12-24 15:34:43 +01:00
34 changed files with 2267 additions and 1429 deletions

View File

@ -13,12 +13,12 @@ jobs:
steps:
- name: Set up Javascript/Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18.14.0
node-version: 18.19.0
- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: '0'
@ -49,12 +49,12 @@ jobs:
steps:
- name: Set up nodejs
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18.14.0
node-version: 18.19.0
- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: '0'

View File

@ -1,5 +1,5 @@
# -- trivial container for BGPalerter
FROM node:18.14.0-alpine as build
FROM node:18.19.0-alpine as build
WORKDIR /opt/bgpalerter
COPY . .

View File

@ -7,11 +7,11 @@ npm ci --silent
npm run compile
./node_modules/.bin/pkg ./dist/package.json --targets node18-win-x64 --output bin/bgpalerter-win-x64 --loglevel=error
./node_modules/.bin/pkg ./dist/package.json --options "no-warnings,max-old-space-size=4096" --targets node18-win-x64 --output bin/bgpalerter-win-x64 --loglevel=error
./node_modules/.bin/pkg ./dist/package.json --targets node18-linux-x64 --output bin/bgpalerter-linux-x64 --loglevel=error
./node_modules/.bin/pkg ./dist/package.json --options "no-warnings,max-old-space-size=4096" --targets node18-linux-x64 --output bin/bgpalerter-linux-x64 --loglevel=error
./node_modules/.bin/pkg ./dist/package.json --targets node18-macos-x64 --output bin/bgpalerter-macos-x64 --loglevel=error
./node_modules/.bin/pkg ./dist/package.json --options "no-warnings,max-old-space-size=4096" --targets node18-macos-x64 --output bin/bgpalerter-macos-x64 --loglevel=error
echo "--> BGPalerter compiled in bin/ (ignore the warnings about files that cannot be resolved)."

View File

@ -46,6 +46,7 @@ monitors:
channel: misconfiguration
name: asn-monitor
params:
skipPrefixMatch: false
thresholdMinPeers: 3
- file: monitorRPKI

View File

@ -59,4 +59,5 @@ Please, let me know so I can add your company name here.
* Speakup (AS49627)
* Nick Bouwhuis (AS202585)
* IX.br (AS26162, AS263044, AS20121)
* Cyberfusion (AS204983)
* Cyberfusion (AS204983)
* EscapeNet (AS7600)

View File

@ -104,7 +104,7 @@ fi
# Delete log file if larger than 5MB
find $LOGS -type f -name "upgrade.log" -size +5M -delete
exec 1> $LOGS/upgrade.log 2>&1
exec 1>> $LOGS/upgrade.log 2>&1
cd $DIR

View File

@ -89,11 +89,11 @@ export default class myMonitor extends Monitor { // It MUST extend Monitor
* This is where the real analysis happens and when the alerts are generated. Place here your complex filtering/analysis.
* The 'filter' function described before is needed to avoid useless calls to the 'monitor' function, which is much more expensive in terms of memory. */
const matchedRule = this.getMoreSpecificMatch(message.prefix); //The method getMoreSpecificMatch is inherited from the super class, it provides the rule in prefixes.yml that matches the current BGP message.
const matchedRules = this.getMoreSpecificMatches(message.prefix); //The method getMoreSpecificMatches is inherited from the super class, it provides the rule in prefixes.yml that matches the current BGP message.
if (matchedRule) { // We matched something in prefixes.yml
for (let matchedRule of matchedRules) { // We matched something in prefixes.yml
const signature = message.originAS.getId() + "-" + message.prefix; // All messages with the same origin AS and prefix will be bundled together. Read above the squash method to understand why.
this.publishAlert(signature, // The method publishAlert is inherited from the super class.
message.prefix, // The monitored resource subject of the alert (it can be an AS or a prefix)
matchedRule, // The monitored rule that was matched (from prefixes.yml)

View File

@ -33,6 +33,7 @@
import yargs from 'yargs';
import fs from "fs";
import yaml from "js-yaml";
import os from "os";
const params = yargs
.usage('Usage: $0 <command> [options]')
@ -51,6 +52,11 @@ const params = yargs
.nargs('t', 0)
.describe('t', 'Test the configuration with fake BGP updates')
.alias('M', 'skip-memory-check')
.nargs('M', 0)
.describe('M', 'Skip memory check')
.alias('d', 'data-volume')
.nargs('d', 1)
.describe('d', 'A directory where configuration and data is persisted')
@ -194,6 +200,11 @@ switch(params._[0]) {
break;
default: // Run monitor
if (!params.M && os.totalmem() < 4294967296) {
throw new Error("You need 4GB or RAM to run BGPalerter");
}
global.DRY_RUN = !!params.t;
if (global.DRY_RUN) console.log("Testing BGPalerter configuration. WARNING: remove -t option for production monitoring.");
const Worker = require("./src/worker").default;

3149
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -44,13 +44,13 @@
"measurements"
],
"devDependencies": {
"@babel/cli": "^7.22.10",
"@babel/core": "^7.22.11",
"@babel/node": "^7.22.10",
"@babel/cli": "^7.23.4",
"@babel/core": "^7.23.6",
"@babel/node": "^7.22.19",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
"@babel/preset-env": "^7.22.14",
"chai": "^4.3.8",
"@babel/preset-env": "^7.23.6",
"chai": "^4.3.10",
"chai-subset": "^1.6.0",
"mocha": "^10.2.0",
"pkg": "^5.8.1",
@ -58,28 +58,30 @@
"syslogd": "^1.1.2"
},
"dependencies": {
"@sentry/node": "^7.66.0",
"axios": "=0.27.2",
"@sentry/node": "^7.91.0",
"batch-promises": "^0.0.3",
"brembo": "^2.1.0",
"deepmerge": "^4.3.1",
"fast-file-logger": "^1.1.5",
"https-proxy-agent": "^5.0.1",
"inquirer": "=8.2.5",
"ip-sub": "^1.3.9",
"https-proxy-agent": "^7.0.2",
"inquirer": "=8.2.6",
"ip-sub": "^1.5.1",
"js-yaml": "^4.1.0",
"kafkajs": "^2.2.4",
"longest-prefix-match": "^1.2.6",
"md5": "^2.3.0",
"moment": "^2.29.4",
"node-cleanup": "^2.1.2",
"nodemailer": "^6.9.4",
"nodemailer": "^6.9.7",
"object-fingerprint": "^1.0.2",
"path": "^0.12.7",
"redaxios": "^0.5.1",
"restify": "github:massimocandela/node-restify",
"rpki-validator": "^2.13.1",
"rpki-validator": "^2.13.9",
"semver": "^7.5.4",
"syslog-client": "^1.1.1",
"uuid": "^9.0.0",
"ws": "^8.13.0",
"uuid": "^9.0.1",
"ws": "^8.15.1",
"yargs": "^17.7.2"
},
"pkg": {
@ -98,7 +100,7 @@
]
},
"optionalDependencies": {
"bufferutil": "^4.0.7",
"bufferutil": "^4.0.8",
"utf-8-validate": "^6.0.3"
}
}

View File

@ -1,4 +1,4 @@
import axios from "axios";
import axios from "redaxios";
export default class Config {
static configVersion = 2;
@ -68,6 +68,7 @@ export default class Config {
channel: "misconfiguration",
name: "asn-monitor",
params: {
skipPrefixMatch: false,
thresholdMinPeers: 3
}
},

View File

@ -31,7 +31,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import axios from "axios";
import axios from "redaxios";
import axiosEnrich from "../utils/axiosEnrich";
export default class Connector {

View File

@ -194,7 +194,7 @@ config.connectors = config.connectors
});
if (config.httpProxy) {
const HttpsProxyAgent = require("https-proxy-agent");
const {HttpsProxyAgent} = require("https-proxy-agent");
vector.agent = new HttpsProxyAgent(url.parse(config.httpProxy));
}

View File

@ -1,4 +1,4 @@
import axios from "axios";
import axios from "redaxios";
import url from "url";
import brembo from "brembo";
import merge from "deepmerge";
@ -41,7 +41,7 @@ module.exports = function generatePrefixes(inputParameters) {
let proxy;
if (httpProxy) {
const HttpsProxyAgent = require("https-proxy-agent");
const {HttpsProxyAgent} = require("https-proxy-agent");
proxy = new HttpsProxyAgent(url.parse(httpProxy));
}
axiosEnrich(axios, proxy, clientId);

View File

@ -33,36 +33,24 @@
import ipUtils from "ip-sub";
import inquirer from "inquirer";
import generatePrefixes from "../generatePrefixesList";
import LongestPrefixMatch from "longest-prefix-match";
export default class Input {
constructor(env){
this.prefixes = [];
this.asns = [];
this.cache = {
af: {},
binaries: {},
matched: {}
};
this.config = env.config;
this.storage = env.storage;
this.logger = env.logger;
this.callbacks = [];
this.prefixListDiffFailThreshold = 50;
// This implements a fast basic fixed space cache, other approaches lru-like use too much cpu
setInterval(() => {
if (Object.keys(this.cache.matched).length > 10000) {
this.cache.matched = {};
}
}, 10000);
this.index = new LongestPrefixMatch();
// This is to load the prefixes after the application is booted
setTimeout(() => {
this.loadPrefixes()
.then(() => {
this._change();
})
.then(() => this._change())
.catch(error => {
this.logger.log({
level: 'error',
@ -103,6 +91,17 @@ export default class Input {
};
_change = () => {
for (let item of this.asns) {
item.group = [item.group].flat();
}
this.index = new LongestPrefixMatch();
for (let item of this.prefixes) {
item.group = [item.group].flat();
this.index.addPrefix(item.prefix, {...item});
}
for (let call of this.callbacks) {
call();
}
@ -144,44 +143,18 @@ export default class Input {
throw new Error('The method getMonitoredPrefixes MUST be implemented');
};
getMoreSpecificMatch = (prefix, includeIgnoredMorespecifics) => {
const key = `${prefix}-${includeIgnoredMorespecifics}`;
const cached = this.cache.matched[key];
if (cached !== undefined) {
return cached;
} else {
for (let p of this.prefixes) {
if (ipUtils._isEqualPrefix(p.prefix, prefix)) {
this.cache.matched[key] = p;
return p;
} else {
_filterIgnoreMorespecifics = (i, prefix, includeIgnoredMorespecifics) => {
return includeIgnoredMorespecifics
|| !i.ignoreMorespecifics
|| ipUtils._isEqualPrefix(i.prefix, prefix); // last piece says "or it is not a more specific"
}
if (!this.cache.af[p.prefix]) {
this.cache.af[p.prefix] = ipUtils.getAddressFamily(p.prefix);
this.cache.binaries[p.prefix] = ipUtils.applyNetmask(p.prefix, this.cache.af[p.prefix]);
}
const prefixAf = ipUtils.getAddressFamily(prefix);
if (prefixAf === this.cache.af[p.prefix]) {
const prefixBinary = ipUtils.applyNetmask(prefix, prefixAf);
if (ipUtils.isSubnetBinary(this.cache.binaries[p.prefix], prefixBinary)) {
if (includeIgnoredMorespecifics || !p.ignoreMorespecifics) {
this.cache.matched[key] = p;
return p;
} else {
this.cache.matched[key] = null;
return null;
}
}
}
}
}
}
return null;
};
getMoreSpecificMatches = (prefix, includeIgnoredMorespecifics=false) => {
return this.index.getMatch(prefix, false)
.filter(i => this._filterIgnoreMorespecifics(i, prefix, includeIgnoredMorespecifics))
.map(i => ({...i}));
}
getMonitoredASns = () => {
throw new Error('The method getMonitoredASns MUST be implemented');

View File

@ -74,9 +74,7 @@ export default class InputYml extends Input {
this.prefixes = [];
this.asns = [];
this._loadPrefixes()
.then(() => {
return this._change();
})
.then(() => this._change())
.catch(error => {
this.logger.log({
level: 'error',
@ -132,7 +130,7 @@ export default class InputYml extends Input {
uniqueAsns[asn] = true;
const item = Object.assign({
asn: new AS(asn),
group: 'default'
group: ['default']
}, monitoredPrefixesFile.options.monitorASns[asn]);
if (item.upstreams && item.upstreams.length) {
@ -162,7 +160,7 @@ export default class InputYml extends Input {
return Object.assign({
prefix: i,
group: 'default',
group: ['default'],
ignore: false,
excludeMonitors: [],
includeMonitors: [],
@ -351,7 +349,7 @@ export default class InputYml extends Input {
for (let asnRule of this.asns) {
monitorASns[asnRule.asn.getValue()] = {
group: asnRule.group,
group: [asnRule.group].flat(),
upstreams: asnRule.upstreams ? asnRule.upstreams.numbers : null,
downstreams: asnRule.downstreams ? asnRule.downstreams.numbers : null,
};

View File

@ -31,7 +31,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import axios from "axios";
import axios from "redaxios";
import axiosEnrich from "../utils/axiosEnrich";
export default class Monitor {
@ -257,31 +257,31 @@ export default class Monitor {
return null;
};
_included = (matched) => {
if (matched.includeMonitors.length > 0) {
return matched.includeMonitors.includes(this.name);
} else {
return !matched.excludeMonitors.includes(this.name);
}
};
_filterMatched = (verbose) => {
return matched => {
if (matched) {
const included = matched.includeMonitors.length > 0
? matched.includeMonitors.includes(this.name)
: !matched.excludeMonitors.includes(this.name);
getMoreSpecificMatch = (prefix, includeIgnoredMorespecifics, verbose=false) => {
const matched = this.input.getMoreSpecificMatch(prefix, includeIgnoredMorespecifics);
if (matched) {
const included = this._included(matched);
if (verbose) {
return {
matched,
included
};
} else if (included) {
return matched;
if (verbose) {
return {
matched,
included
};
} else if (included) {
return matched;
}
}
}
return null;
return null;
}
}
getMoreSpecificMatches = (prefix, includeIgnoredMorespecifics, verbose) => {
return this.input.getMoreSpecificMatches(prefix, includeIgnoredMorespecifics)
.map(this._filterMatched(verbose))
.filter(i => !!i);
};
}

View File

@ -37,6 +37,7 @@ export default class MonitorAS extends Monitor {
constructor(name, channel, params, env, input){
super(name, channel, params, env, input);
this.thresholdMinPeers = (params && params.thresholdMinPeers != null) ? params.thresholdMinPeers : 3;
this.skipPrefixMatch = !!params?.skipPrefixMatch;
this.updateMonitoredResources();
};
@ -68,7 +69,7 @@ export default class MonitorAS extends Monitor {
if (prefixesOut.length > 1) {
return `${matchedMessages[0].originAS} is announcing some prefixes which are not in the configured list of announced prefixes: ${prefixesOut}`
} else if (prefixesOut.length === 1) {
return `${matchedMessages[0].originAS} is announcing ${matchedMessages[0].prefix} but this prefix is not in the configured list of announced prefixes`;
return `${matchedMessages[0].originAS} is announcing ${matchedMessages[0].prefix} but this prefix is not in the configured list of announced prefixes`;
}
return false;
@ -79,15 +80,34 @@ export default class MonitorAS extends Monitor {
const messageOrigin = message.originAS;
const messagePrefix = message.prefix;
const matchedRule = this.getMonitoredAsMatch(messageOrigin);
const matchedASRule = this.getMonitoredAsMatch(messageOrigin);
if (matchedRule) {
if (matchedASRule) {
const matchedPrefixRules = this.getMoreSpecificMatches(messagePrefix, true, false);
if (this.skipPrefixMatch) {
const skipMatches = matchedPrefixRules.map(i => i.group).flat();
const goodMatches = [matchedASRule.group].flat();
for (let g of goodMatches) {
if (!skipMatches.includes(g)) {
this.publishAlert(messageOrigin.getId().toString() + "-" + messagePrefix,
messageOrigin.getId(),
{
...matchedASRule,
group: [g]
},
message,
{});
}
}
} else if (!matchedPrefixRules.length) {
const matchedPrefixRule = this.getMoreSpecificMatch(messagePrefix, true);
if (!matchedPrefixRule) {
this.publishAlert(messageOrigin.getId().toString() + "-" + messagePrefix,
messageOrigin.getId(),
matchedRule,
matchedASRule,
message,
{});
}

View File

@ -80,15 +80,17 @@ export default class MonitorHijack extends Monitor {
}
}
monitor = (message) =>
new Promise((resolve, reject) => {
const messagePrefix = message.prefix;
const matchedRule = this.getMoreSpecificMatch(messagePrefix, false);
monitor = (message) =>{
const messagePrefix = message.prefix;
const matchedRules = this.getMoreSpecificMatches(messagePrefix, false);
if (matchedRule && !matchedRule.ignore && !matchedRule.asn.includes(message.originAS)) {
for (let matchedRule of matchedRules) {
if (!matchedRule.ignore && !matchedRule.asn.includes(message.originAS)) {
this.validate(message, matchedRule);
resolve(true);
}
});
}
return Promise.resolve(true);
}
}

View File

@ -62,13 +62,13 @@ export default class MonitorNewPrefix extends Monitor {
return false;
};
monitor = (message) =>
new Promise((resolve, reject) => {
monitor = (message) => {
const messagePrefix = message.prefix;
const matchedRule = this.getMoreSpecificMatch(messagePrefix, false);
const messagePrefix = message.prefix;
const matchedRules = this.getMoreSpecificMatches(messagePrefix, false);
if (matchedRule && !matchedRule.ignore &&
for (let matchedRule of matchedRules) {
if (!matchedRule.ignore &&
matchedRule.asn.includes(message.originAS) &&
!ipUtils._isEqualPrefix(matchedRule.prefix, messagePrefix)) {
@ -78,8 +78,9 @@ export default class MonitorNewPrefix extends Monitor {
message,
{});
}
}
resolve(true);
});
return Promise.resolve(true);
}
}

View File

@ -104,20 +104,20 @@ export default class MonitorPath extends Monitor {
}
};
monitor = (message) =>
new Promise((resolve, reject) => {
monitor = (message) => {
const messagePrefix = message.prefix;
const matchedRule = this.getMoreSpecificMatch(messagePrefix, false);
const messagePrefix = message.prefix;
const matchedRules = this.getMoreSpecificMatches(messagePrefix, false);
if (matchedRule && !matchedRule.ignore && matchedRule.path) {
for (let matchedRule of matchedRules) {
if (!matchedRule.ignore && matchedRule.path) {
const pathRules = (matchedRule.path.length) ? matchedRule.path : [ matchedRule.path ];
pathRules.map((pathRule, position) => this.pathRuleCheck(pathRule, position, message, matchedRule));
}
}
resolve(true);
});
return Promise.resolve(true);
};
}

View File

@ -135,6 +135,13 @@ export default class MonitorROAS extends Monitor {
vrpCount: sizes[ta],
expiringVrps: expiringSizes[ta]
});
})
.catch(error => {
this.logger.log({
level: 'error',
message: error
});
});
}
}
@ -187,17 +194,8 @@ export default class MonitorROAS extends Monitor {
expiring: items.map(i => i.file)
};
} else {
return {};
return Promise.reject("Not found yet");
}
})
.catch(error => {
this.logger.log({
level: 'error',
message: error
});
return {};
});
} else {
return Promise.resolve({});
@ -210,30 +208,44 @@ export default class MonitorROAS extends Monitor {
return Promise.all([...new Set(vrps.map(i => i.prefix))]
.map(prefix => {
const roas = vrps.filter(i => ipUtils.isEqualPrefix(i.prefix, prefix)); // Get only the ROAs for this prefix
const matchedRule = this.getMoreSpecificMatch(prefix, false); // Get the matching rule
if (matchedRule) {
return this._getExpiringItems(roas)
.then(extra => {
const alertsStrings = [...new Set(roas.map(this._roaToString))];
let message = "";
const matchedRules = this.getMoreSpecificMatches(prefix, false); // Get the matching rule
if (extra && extra.type === "chain") {
message = `The following ROAs will become invalid in less than ${roaExpirationAlertHours} hours: ${alertsStrings.join("; ")}.`
message += ` The reason is the expiration of the following parent components: ${extra.expiring.join(", ")}`;
} else {
message = `The following ROAs will expire in less than ${roaExpirationAlertHours} hours: ${alertsStrings.join("; ")}`;
}
alerts = alerts.concat(alertsStrings);
return Promise
.all(matchedRules
.map(matchedRule => {
return this._getExpiringItems(roas)
.then(extra => {
const alertsStrings = [...new Set(roas.map(this._roaToString))];
let message = "";
this.publishAlert(md5(message), // The hash will prevent alert duplications in case multiple ASes/prefixes are involved
matchedRule.prefix,
matchedRule,
message,
{...extra, vrps, roaExpirationHours: roaExpirationAlertHours, rpkiMetadata: metadata, subType: "roa-expire"});
});
} else {
return Promise.resolve();
}
if (extra && extra.type === "chain") {
message = `The following ROAs will become invalid in less than ${roaExpirationAlertHours} hours: ${alertsStrings.join("; ")}.`
message += ` The reason is the expiration of the following parent components: ${extra.expiring.join(", ")}`;
} else {
message = `The following ROAs will expire in less than ${roaExpirationAlertHours} hours: ${alertsStrings.join("; ")}`;
}
alerts = alerts.concat(alertsStrings);
this.publishAlert(md5(message), // The hash will prevent alert duplications in case multiple ASes/prefixes are involved
matchedRule.prefix,
matchedRule,
message,
{
...extra,
vrps,
roaExpirationHours: roaExpirationAlertHours,
rpkiMetadata: metadata,
subType: "roa-expire"
});
})
.catch(error => {
this.logger.log({
level: 'error',
message: error
});
});
}))
}))
.then(() => alerts);
};
@ -265,7 +277,20 @@ export default class MonitorROAS extends Monitor {
matchedRule.asn.getId(),
matchedRule,
message,
{...extra, vrps: unsentVrps, roaExpirationHours: roaExpirationAlertHours, rpkiMetadata: metadata, subType: "roa-expire"});
{
...extra,
vrps: unsentVrps,
roaExpirationHours: roaExpirationAlertHours,
rpkiMetadata: metadata,
subType: "roa-expire"
});
})
.catch(error => {
this.logger.log({
level: 'error',
message: error
});
});
}
}
@ -314,20 +339,26 @@ export default class MonitorROAS extends Monitor {
for (let prefix of [...new Set(roaDiff.map(i => i.prefix))]) {
const roas = roaDiff.filter(i => ipUtils.isEqualPrefix(i.prefix, prefix)); // Get only the ROAs for this prefix
const matchedRule = this.getMoreSpecificMatch(prefix, false); // Get the matching rule
if (matchedRule) {
const matchedRules = this.getMoreSpecificMatches(prefix, false); // Get the matching rule
for (let matchedRule of matchedRules) {
const alertsStrings = [...new Set(roas.map(this._roaToString))];
const message = alertsStrings.length <= 10 ?
`ROAs change detected: ${alertsStrings.join("; ")}` :
`ROAs change detected: ${alertsStrings.slice(0, 10).join("; ")} and more...`;
alerts = alerts.concat(alertsStrings);
const metadata = this.rpki.getMetadata();
this.publishAlert(md5(message), // The hash will prevent alert duplications in case multiple ASes/prefixes are involved
matchedRule.prefix,
matchedRule,
message,
{diff: alertsStrings, subType: "roa-diff"});
{
diff: alertsStrings,
subType: "roa-diff",
rpkiMetadata: metadata
});
}
}
}
@ -358,12 +389,17 @@ export default class MonitorROAS extends Monitor {
`ROAs change detected: ${alertsStrings.join("; ")}` :
`ROAs change detected: ${alertsStrings.slice(0, 10).join("; ")} and more...`;
alerts = alerts.concat(alertsStrings);
const metadata = this.rpki.getMetadata();
this.publishAlert(md5(message), // The hash will prevent alert duplications in case multiple ASes/prefixes are involved
matchedRule.asn.getId(),
matchedRule,
message,
{diff: alertsStrings, subType: "roa-diff"});
{
diff: alertsStrings,
subType: "roa-diff",
rpkiMetadata: metadata
});
}
}
}

View File

@ -107,6 +107,8 @@ export default class MonitorRPKI extends Monitor {
const origin = result.origin.getValue();
if (result && !this.rpki.getStatus().stale) {
const rpkiMetadata = this.rpki.getMetadata();
const cacheKey = "a" + [prefix, origin]
.join("-")
.replace(/\./g, "_")
@ -123,20 +125,20 @@ export default class MonitorRPKI extends Monitor {
prefix,
matchedRule,
message,
{ covering: null, valid: null, roaDisappeared: true, subType: "rpki-disappear" });
{ rpkiMetadata, covering: null, valid: null, roaDisappeared: true, subType: "rpki-disappear" });
} else if (this.params.checkUncovered) {
this.publishAlert(key,
prefix,
matchedRule,
message,
{ covering: null, valid: null, subType: "rpki-unknown" });
{ rpkiMetadata, covering: null, valid: null, subType: "rpki-unknown" });
}
} else if (result.valid === false) {
this.publishAlert(key,
prefix,
matchedRule,
message,
{ covering: result.covering, valid: false, subType: "rpki-invalid" });
{ rpkiMetadata, covering: result.covering, valid: false, subType: "rpki-invalid" });
} else if (result.valid) {
@ -183,18 +185,23 @@ export default class MonitorRPKI extends Monitor {
const messageOrigin = message.originAS;
const prefix = message.prefix;
const matchedPrefixRule = this.getMoreSpecificMatch(prefix, false, true);
const matchedPrefixRules = this.getMoreSpecificMatches(prefix, false, true);
if (matchedPrefixRule && matchedPrefixRule.matched) { // There is a prefix match
if (!matchedPrefixRule.matched.ignore && matchedPrefixRule.included) { // The prefix match is not excluded in any way
this.validate(message, matchedPrefixRule.matched);
if (matchedPrefixRules.length) {
for (let matchedPrefixRule of matchedPrefixRules) {
if (matchedPrefixRule.matched) { // There is a prefix match
if (!matchedPrefixRule.matched.ignore && matchedPrefixRule.included) { // The prefix match is not excluded in any way
this.validate(message, matchedPrefixRule.matched);
}
}
}
} else { // No prefix match
} else {
const matchedASRule = this.getMonitoredAsMatch(messageOrigin); // Try AS match
if (matchedASRule) {
this.validate(message, matchedASRule);
}
}
} catch (error) {
this.logger.log({
level: 'error',

View File

@ -68,13 +68,12 @@ export default class MonitorVisibility extends Monitor {
}
};
monitor = (message) =>
new Promise((resolve, reject) => {
monitor = (message) => {
const messagePrefix = message.prefix;
const matchedRules = this.getMoreSpecificMatches(messagePrefix, false);
const messagePrefix = message.prefix;
const matchedRule = this.getMoreSpecificMatch(messagePrefix, false);
if (matchedRule && !matchedRule.ignore && ipUtils._isEqualPrefix(matchedRule.prefix, messagePrefix)) {
for (let matchedRule of matchedRules) {
if (!matchedRule.ignore && ipUtils._isEqualPrefix(matchedRule.prefix, messagePrefix)) {
let key = matchedRule.prefix;
@ -84,8 +83,8 @@ export default class MonitorVisibility extends Monitor {
message,
{});
}
}
resolve(true);
});
return Promise.resolve(true);
}
}

View File

@ -30,7 +30,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import axios from "axios";
import axios from "redaxios";
import env from "../env";
import axiosEnrich from "../utils/axiosEnrich";

View File

@ -33,7 +33,7 @@
import moment from "moment";
import brembo from "brembo";
import axios from "axios";
import axios from "redaxios";
import axiosEnrich from "../utils/axiosEnrich";
import RpkiValidator from "rpki-validator";

View File

@ -3,14 +3,14 @@ import md5 from "md5";
const attempts = {};
const numAttempts = 2;
const retry = function (axios, error) {
const retry = function (axios, error, params) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const key = md5(JSON.stringify(error.config));
const key = md5(JSON.stringify(params));
attempts[key] = attempts[key] || 0;
attempts[key]++;
if (attempts[key] <= numAttempts) {
resolve(axios.request(error.config));
resolve(axios.request(params));
} else {
reject(error);
}
@ -20,6 +20,10 @@ const retry = function (axios, error) {
export default function(axios, httpsAgent, userAgent) {
axios.defaults ??= {};
axios.defaults.headers ??= {};
axios.defaults.headers.common ??= {};
// Set agent/proxy
if (httpsAgent) {
axios.defaults.httpsAgent = httpsAgent;
@ -36,15 +40,6 @@ export default function(axios, httpsAgent, userAgent) {
'Accept-Encoding': 'gzip'
};
// Retry
axios.interceptors.response.use(
response => response,
error => {
if (error.config) {
return retry(axios, error);
}
return Promise.reject(error);
});
return axios;
return params => axios(params)
.catch(error => retry(axios, error, params));
}

View File

@ -1,9 +1,9 @@
import RpkiValidator from "rpki-validator";
import fs from "fs";
import md5 from "md5";
import axiosEnrich from "./axiosEnrich";
import axios from "axios";
import axios from "redaxios";
import moment from "moment";
import fingerprint from "object-fingerprint";
export default class RpkiUtils {
constructor(env) {
@ -301,7 +301,8 @@ export default class RpkiUtils {
_markAsStale = () => {
if (!!this.params.preCacheROAs) {
const digest = md5(JSON.stringify(this.getVRPs()));
const digest = fingerprint(this.getVRPs());
if (this.oldDigest) {
const stale = this.oldDigest === digest;

View File

@ -241,7 +241,7 @@ describe("Core functions", function() {
"description": "rpki valid not monitored AS",
"ignoreMorespecifics": false,
"prefix": "193.0.0.0/21",
"group": "default",
"group": ["default"],
"excludeMonitors" : [],
"includeMonitors": []
},
@ -250,7 +250,7 @@ describe("Core functions", function() {
"description": "description 1",
"ignoreMorespecifics": false,
"prefix": "165.254.225.0/24",
"group": "default",
"group": ["default"],
"ignore": false,
"excludeMonitors" : [],
"includeMonitors": []
@ -260,7 +260,7 @@ describe("Core functions", function() {
"description": "description 2",
"ignoreMorespecifics": false,
"prefix": "165.254.255.0/24",
"group": "groupName",
"group": ["groupName"],
"ignore": false,
"excludeMonitors" : [],
"includeMonitors": []
@ -270,7 +270,7 @@ describe("Core functions", function() {
"description": "description 3",
"ignoreMorespecifics": true,
"prefix": "192.147.168.0/24",
"group": "default",
"group": ["default"],
"ignore": false,
"excludeMonitors" : [],
"includeMonitors": []
@ -280,7 +280,7 @@ describe("Core functions", function() {
"description": "alarig fix test",
"ignoreMorespecifics": false,
"prefix": "2a00:5884::/32",
"group": "default",
"group": ["default"],
"ignore": false,
"excludeMonitors" : [],
"includeMonitors": []
@ -290,7 +290,7 @@ describe("Core functions", function() {
"description": "alarig fix test 2",
"ignoreMorespecifics": false,
"prefix": "2a0e:f40::/29",
"group": "default",
"group": ["default"],
"ignore": false,
"excludeMonitors" : [],
"includeMonitors": []
@ -300,7 +300,7 @@ describe("Core functions", function() {
"description": "ignore sub test",
"ignoreMorespecifics": true,
"prefix": "2a0e:f40::/30",
"group": "default",
"group": ["default"],
"ignore": false,
"excludeMonitors" : [],
"includeMonitors": []
@ -310,7 +310,7 @@ describe("Core functions", function() {
"description": "ignore flag test",
"ignoreMorespecifics": true,
"prefix": "2a0e:240::/32",
"group": "default",
"group": ["default"],
"ignore": true,
"excludeMonitors" : [],
"includeMonitors": []
@ -320,7 +320,7 @@ describe("Core functions", function() {
"description": "include exclude test",
"ignoreMorespecifics": false,
"prefix": "175.254.205.0/24",
"group": "default",
"group": ["default"],
"ignore": false,
"excludeMonitors" : ["basic-hijack-detection", "withdrawal-detection"],
"includeMonitors": []
@ -330,7 +330,7 @@ describe("Core functions", function() {
"description": "include exclude test",
"ignoreMorespecifics": false,
"prefix": "170.254.205.0/24",
"group": "default",
"group": ["default"],
"ignore": false,
"excludeMonitors" : [],
"includeMonitors": ["prefix-detection"]
@ -340,7 +340,7 @@ describe("Core functions", function() {
"description": "test fade off",
"ignoreMorespecifics": false,
"prefix": "165.24.225.0/24",
"group": "default",
"group": ["default"],
"ignore": false,
"excludeMonitors" : [],
"includeMonitors": []
@ -350,7 +350,7 @@ describe("Core functions", function() {
"description": "exact matching test",
"ignoreMorespecifics": true,
"prefix": "2001:db8:123::/48",
"group": "default",
"group": ["default"],
"ignore": false,
"excludeMonitors" : [],
"includeMonitors": []

View File

@ -40,7 +40,7 @@ const cacheCloneDirectory = "tests/.cache_clone/";
const asyncTimeout = 120000;
global.EXTERNAL_VERSION_FOR_TEST = "0.0.1";
global.EXTERNAL_CONFIG_FILE = volume + "config.test.yml";
const axios = require("axios");
const axios = require("redaxios");
const worker = require("../index");
const pubSub = worker.pubSub;
@ -120,7 +120,7 @@ describe("Alerting", function () {
extra: {},
matchedRule: {
prefix: "165.254.255.0/24",
group: "groupName",
group: ["groupName"],
description: "description 2",
asn: [15562],
ignoreMorespecifics: false
@ -146,9 +146,9 @@ describe("Alerting", function () {
extra: {},
matchedRule:{
prefix:"2a00:5884::/32",
group:"default",
group: ["default"],
description:"alarig fix test",
asn:[204092, 45],
asn: [204092, 45],
ignoreMorespecifics:false
},
matchedMessage: {
@ -172,18 +172,18 @@ describe("Alerting", function () {
extra: {},
matchedRule:{
prefix: "2a00:5884::/32",
group: "default",
group: ["default"],
description: "alarig fix test",
asn:[204092, 45],
asn: [204092, 45],
ignoreMorespecifics: false
},
matchedMessage: {
type: "announcement",
prefix: "2a00:5884::/32",
peer:"124.0.0.3",
path:[1,2,3,15563],
peer: "124.0.0.3",
path: [1,2,3,15563],
originAS: [15563],
nextHop:"124.0.0.3"
nextHop: "124.0.0.3"
}
}
]
@ -208,7 +208,7 @@ describe("Alerting", function () {
setTimeout(() => {
hijackTestCompleted = true;
done();
}, 5000);
}, 10000);
}
}
} catch (error) {
@ -234,7 +234,7 @@ describe("Alerting", function () {
extra: {},
matchedRule: {
prefix: '175.254.205.0/24',
group: 'default',
group: ['default'],
description: 'include exclude test',
asn: [1234],
ignoreMorespecifics: false,
@ -262,7 +262,7 @@ describe("Alerting", function () {
extra: {},
matchedRule: {
prefix: '170.254.205.0/24',
group: 'default',
group: ['default'],
description: 'include exclude test',
asn: [1234],
ignoreMorespecifics: false,
@ -291,7 +291,7 @@ describe("Alerting", function () {
extra: {},
matchedRule: {
prefix: '165.254.255.0/24',
group: 'groupName',
group: ['groupName'],
description: 'description 2',
asn: [15562],
ignoreMorespecifics: false
@ -317,7 +317,7 @@ describe("Alerting", function () {
extra: {},
matchedRule: {
prefix: '2a00:5884::/32',
group: 'default',
group: ['default'],
description: 'alarig fix test',
asn: [ 204092, 45],
ignoreMorespecifics: false
@ -386,7 +386,7 @@ describe("Alerting", function () {
"affected": "98.5.4.3/22",
"matchedRule": {
"prefix": "98.5.4.3/22",
"group": "default",
"group": ["default"],
"ignore": false,
"excludeMonitors": [],
"includeMonitors": [],
@ -431,7 +431,7 @@ describe("Alerting", function () {
"affected": "99.5.4.3/22",
"matchedRule": {
"prefix": "99.5.4.3/22",
"group": "default",
"group": ["default"],
"ignore": false,
"excludeMonitors": [],
"includeMonitors": [],

View File

@ -32,7 +32,7 @@
const chai = require("chai");
const chaiSubset = require('chai-subset');
const axios = require('axios');
const axios = require('redaxios');
chai.use(chaiSubset);
const expect = chai.expect;
const volume = "volumetests/";

View File

@ -35,6 +35,7 @@ monitors:
channel: misconfiguration
name: asn-monitor
params:
skipPrefixMatch: false
thresholdMinPeers: 2
- file: monitorRPKI

View File

@ -70,7 +70,7 @@ describe("Alerting", function () {
"affected": 101,
"matchedRule": {
"asn": [101],
"group": "default",
"group": ["default"],
"groupd": "default",
"upstreams": [100],
"downstreams": [104]
@ -99,7 +99,7 @@ describe("Alerting", function () {
"affected": 80,
"matchedRule": {
"asn": [80],
"group": "default",
"group": ["default"],
"groupd": "default",
"upstreams": [99],
"downstreams": null
@ -128,7 +128,7 @@ describe("Alerting", function () {
"affected": 101,
"matchedRule": {
"asn": [101],
"group": "default",
"group": ["default"],
"groupd": "default",
"upstreams": [100],
"downstreams": [104]

View File

@ -34,7 +34,7 @@ const chai = require("chai");
const chaiSubset = require('chai-subset');
chai.use(chaiSubset);
const expect = chai.expect;
const axios = require('axios');
const axios = require('redaxios');
const asyncTimeout = 120000;
global.EXTERNAL_VERSION_FOR_TEST = "0.0.1";