mirror of
https://github.com/nttgin/BGPalerter.git
synced 2024-05-19 06:50:08 +00:00
first version monitorAS (#68)
This commit is contained in:
@@ -38,6 +38,12 @@ monitors:
|
||||
params:
|
||||
thresholdMinPeers: 10
|
||||
|
||||
- file: MonitorAS
|
||||
channel: misconfiguration
|
||||
name: asn-monitor
|
||||
params:
|
||||
thresholdMinPeers: 0
|
||||
|
||||
reports:
|
||||
- file: reportFile
|
||||
channels:
|
||||
@@ -45,6 +51,7 @@ reports:
|
||||
- newprefix
|
||||
- visibility
|
||||
- path
|
||||
- misconfiguration
|
||||
|
||||
# - file: reportEmail
|
||||
# channels:
|
||||
@@ -52,6 +59,7 @@ reports:
|
||||
# - newprefix
|
||||
# - visibility
|
||||
# - path
|
||||
# - misconfiguration
|
||||
# params:
|
||||
# showPaths: 5 # Amount of AS_PATHs to report in the alert
|
||||
# senderEmail: bgpalerter@xxxx
|
||||
@@ -73,13 +81,14 @@ reports:
|
||||
# default:
|
||||
# - joe@example.org
|
||||
# - noc@example.org
|
||||
#
|
||||
|
||||
# - file: reportSlack
|
||||
# channels:
|
||||
# - hijack
|
||||
# - newprefix
|
||||
# - visibility
|
||||
# - path
|
||||
# - misconfiguration
|
||||
# params:
|
||||
# colors:
|
||||
# hijack: '#d60b1c'
|
||||
@@ -88,13 +97,14 @@ reports:
|
||||
# path: '#42cbf5'
|
||||
# hooks:
|
||||
# default: _YOUR_SLACK_WEBHOOK_URL_
|
||||
#
|
||||
|
||||
# - file: reportKafka
|
||||
# channels:
|
||||
# - hijack
|
||||
# - newprefix
|
||||
# - visibility
|
||||
# - path
|
||||
# - misconfiguration
|
||||
# params:
|
||||
# host: localhost:9092
|
||||
# topics:
|
||||
|
@@ -15,3 +15,11 @@
|
||||
asn: 15562
|
||||
ignoreMorespecifics: false
|
||||
ignore: false
|
||||
|
||||
|
||||
options:
|
||||
monitorASns:
|
||||
2914:
|
||||
group: default
|
||||
3333:
|
||||
group: default
|
@@ -145,14 +145,33 @@ export default class ConnectorRIS extends Connector{
|
||||
}
|
||||
};
|
||||
|
||||
_subscribeToASns = (input) => {
|
||||
const monitoredASns = input.getMonitoredASns().map(i => i.asn);
|
||||
|
||||
const params = JSON.parse(JSON.stringify(this.params.subscription));
|
||||
for (let asn of monitoredASns){
|
||||
|
||||
console.log("Monitoring AS", asn.getValue());
|
||||
params.path = '' + asn.getValue() + '$';
|
||||
|
||||
this.ws.send(JSON.stringify({
|
||||
type: "ris_subscribe",
|
||||
data: params
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
subscribe = (input) =>
|
||||
new Promise((resolve, reject) => {
|
||||
this.subscription = input;
|
||||
try {
|
||||
(this.params.carefulSubscription) ?
|
||||
this._subscribeToPrefixes(input) :
|
||||
if (this.params.carefulSubscription) {
|
||||
this._subscribeToPrefixes(input);
|
||||
this._subscribeToASns(input);
|
||||
} else {
|
||||
this._subscribeToAll(input);
|
||||
}
|
||||
|
||||
resolve(true);
|
||||
} catch(error) {
|
||||
@@ -163,48 +182,56 @@ export default class ConnectorRIS extends Connector{
|
||||
|
||||
static transform = (message) => {
|
||||
if (message.type === 'ris_message') {
|
||||
message = message.data;
|
||||
const components = [];
|
||||
const announcements = message["announcements"] || [];
|
||||
const aggregator = message["aggregator"] || null;
|
||||
const withdrawals = message["withdrawals"] || [];
|
||||
const peer = message["peer"];
|
||||
let path, originAS;
|
||||
if (message["path"] && message["path"].length) {
|
||||
path = new Path(message["path"].map(i => new AS(i)));
|
||||
originAS = path.getLast();
|
||||
} else {
|
||||
path = new Path([]);
|
||||
originAS = null;
|
||||
}
|
||||
try {
|
||||
message = message.data;
|
||||
const components = [];
|
||||
const announcements = message["announcements"] || [];
|
||||
const aggregator = message["aggregator"] || null;
|
||||
const withdrawals = message["withdrawals"] || [];
|
||||
const peer = message["peer"];
|
||||
let path, originAS;
|
||||
if (message["path"] && message["path"].length) {
|
||||
path = new Path(message["path"].map(i => new AS(i)));
|
||||
originAS = path.getLast();
|
||||
} else {
|
||||
path = new Path([]);
|
||||
originAS = null;
|
||||
}
|
||||
|
||||
for (let announcement of announcements) {
|
||||
const nextHop = announcement["next_hop"];
|
||||
const prefixes = announcement["prefixes"] || [];
|
||||
for (let announcement of announcements) {
|
||||
const nextHop = announcement["next_hop"];
|
||||
const prefixes = announcement["prefixes"] || [];
|
||||
|
||||
for (let prefix of prefixes) {
|
||||
for (let prefix of prefixes) {
|
||||
|
||||
components.push({
|
||||
type: "announcement",
|
||||
prefix,
|
||||
peer,
|
||||
path,
|
||||
originAS,
|
||||
nextHop,
|
||||
aggregator
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (let prefix of withdrawals) {
|
||||
components.push({
|
||||
type: "announcement",
|
||||
type: "withdrawal",
|
||||
prefix,
|
||||
peer,
|
||||
path,
|
||||
originAS,
|
||||
nextHop,
|
||||
aggregator
|
||||
peer
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (let prefix of withdrawals) {
|
||||
components.push({
|
||||
type: "withdrawal",
|
||||
prefix,
|
||||
peer
|
||||
})
|
||||
return components;
|
||||
} catch (error) {
|
||||
throw new Error(`Error during tranform (${this.name}): ` + error.message);
|
||||
}
|
||||
|
||||
return components;
|
||||
} else if (message.type === 'ris_error') {
|
||||
console.log(message);
|
||||
throw new Error("Error from RIS: " + message.data.message);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
@@ -249,6 +249,22 @@ export default class ConnectorTest extends Connector{
|
||||
}
|
||||
];
|
||||
break;
|
||||
|
||||
case "misconfiguration":
|
||||
updates = [
|
||||
{
|
||||
data: {
|
||||
announcements: [{
|
||||
prefixes: ["2.2.2.2/22"],
|
||||
next_hop: "124.0.0.3"
|
||||
}],
|
||||
peer: "124.0.0.3",
|
||||
path: [1, 2, 3, 4321, 5060, 2914]
|
||||
},
|
||||
type: "ris_message"
|
||||
}
|
||||
];
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
@@ -56,7 +56,7 @@ export default class Consumer {
|
||||
try {
|
||||
const connector = data.slice(0,3);
|
||||
const messagesRaw = JSON.parse(data.slice(4));
|
||||
const messages = this.connectors[connector].transform(messagesRaw);
|
||||
const messages = this.connectors[connector].transform(messagesRaw) || [];
|
||||
|
||||
for (let monitor of this.monitors) {
|
||||
|
||||
@@ -78,7 +78,7 @@ export default class Consumer {
|
||||
} catch (error) {
|
||||
env.logger.log({
|
||||
level: 'error',
|
||||
message: "Error in parsing data, dispatch method of consumer.js: " + error
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@@ -37,6 +37,7 @@ export default class Input {
|
||||
|
||||
constructor(config){
|
||||
this.prefixes = [];
|
||||
this.asns = [];
|
||||
this.cache = {};
|
||||
};
|
||||
|
||||
@@ -109,4 +110,8 @@ export default class Input {
|
||||
return null;
|
||||
};
|
||||
|
||||
getMonitoredASns = () => {
|
||||
throw new Error('The method getMonitoredASns MUST be implemented');
|
||||
};
|
||||
|
||||
}
|
@@ -42,12 +42,14 @@ export default class InputYml extends Input {
|
||||
constructor(config){
|
||||
super(config);
|
||||
this.prefixes = [];
|
||||
this.asns = [];
|
||||
|
||||
if (!config.monitoredPrefixesFiles || config.monitoredPrefixesFiles.length === 0) {
|
||||
throw new Error("The monitoredPrefixesFiles key is missing in the config file");
|
||||
}
|
||||
|
||||
const uniquePrefixes = {};
|
||||
const uniqueAsns = {};
|
||||
for (let prefixesFile of config.monitoredPrefixesFiles) {
|
||||
|
||||
let monitoredPrefixesFile = {};
|
||||
@@ -67,8 +69,24 @@ export default class InputYml extends Input {
|
||||
|
||||
if (this.validate(monitoredPrefixesFile)) {
|
||||
|
||||
if (monitoredPrefixesFile.options && monitoredPrefixesFile.options.monitorASns) {
|
||||
this.asns = Object
|
||||
.keys(monitoredPrefixesFile.options.monitorASns)
|
||||
.map(asn => {
|
||||
if (uniqueAsns[asn]) {
|
||||
throw new Error("Duplicate entry for monitored AS " + asn);
|
||||
}
|
||||
uniqueAsns[asn] = true;
|
||||
return Object.assign({
|
||||
asn: new AS(asn),
|
||||
group: 'default'
|
||||
}, monitoredPrefixesFile.options.monitorASns[asn]);
|
||||
});
|
||||
}
|
||||
|
||||
const monitoredPrefixes = Object
|
||||
.keys(monitoredPrefixesFile)
|
||||
.filter(i => i !== "options")
|
||||
.map(i => {
|
||||
if (uniquePrefixes[i]) {
|
||||
throw new Error("Duplicate entry for " + i);
|
||||
@@ -102,8 +120,26 @@ export default class InputYml extends Input {
|
||||
};
|
||||
|
||||
validate = (fileContent) => {
|
||||
const errors = Object
|
||||
let prefixesError, optionsError = [];
|
||||
|
||||
const options = fileContent.options;
|
||||
|
||||
// if (options && options.monitorASns) {
|
||||
// optionsError = Object
|
||||
// .keys(options.monitorASns)
|
||||
// .map(asn => {
|
||||
// console.log(new AS("2914").isValid());
|
||||
// if (!new AS(asn).isValid()) {
|
||||
// return "Not a valid AS number in monitorASns";
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// console.log(optionsError);
|
||||
// }
|
||||
|
||||
prefixesError = Object
|
||||
.keys(fileContent)
|
||||
.filter(i => i !== "options")
|
||||
.map(prefix => {
|
||||
const item = fileContent[prefix];
|
||||
let asns;
|
||||
@@ -171,7 +207,7 @@ export default class InputYml extends Input {
|
||||
throw new Error(error);
|
||||
});
|
||||
|
||||
return errors.length === 0;
|
||||
return [...prefixesError, ...optionsError].length === 0;
|
||||
};
|
||||
|
||||
_validateRegex = (regex) => {
|
||||
@@ -193,4 +229,7 @@ export default class InputYml extends Input {
|
||||
return this.prefixes;
|
||||
};
|
||||
|
||||
getMonitoredASns = () => {
|
||||
return this.asns;
|
||||
};
|
||||
}
|
@@ -36,7 +36,7 @@ export class AS {
|
||||
}
|
||||
|
||||
if (this.isValid()) {
|
||||
this.numbers.map(i => parseInt(i));
|
||||
this.numbers = this.numbers.map(i => parseInt(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export class AS {
|
||||
|
||||
try {
|
||||
const intAsn = parseInt(asn);
|
||||
if (intAsn !== asn) {
|
||||
if (intAsn != asn) {
|
||||
return false;
|
||||
}
|
||||
asn = intAsn;
|
||||
|
@@ -50,12 +50,11 @@ export default class Monitor {
|
||||
checkStaleNotificationsSeconds: 60,
|
||||
clearNotificationQueueAfterSeconds: (this.config.notificationIntervalSeconds * 3) / 2
|
||||
};
|
||||
this.updateMonitoredPrefixes();
|
||||
setInterval(this._publish, this.internalConfig.checkStaleNotificationsSeconds * 1000);
|
||||
};
|
||||
|
||||
updateMonitoredPrefixes = () => {
|
||||
this.monitored = this.input.getMonitoredPrefixes();
|
||||
updateMonitoredResources = () => {
|
||||
throw new Error('The method updateMonitoredResources must be implemented in ' + this.name);
|
||||
};
|
||||
|
||||
monitor = (message) =>
|
||||
|
86
src/monitors/monitorAS.js
Normal file
86
src/monitors/monitorAS.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* BSD 3-Clause License
|
||||
*
|
||||
* Copyright (c) 2019, NTT Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
import Monitor from "./monitor";
|
||||
|
||||
export default class MonitorAS extends Monitor {
|
||||
|
||||
constructor(name, channel, params, env){
|
||||
super(name, channel, params, env);
|
||||
this.thresholdMinPeers = (params && params.thresholdMinPeers != null) ? params.thresholdMinPeers : 0;
|
||||
this.updateMonitoredResources();
|
||||
};
|
||||
|
||||
updateMonitoredResources = () => {
|
||||
this.monitored = this.input.getMonitoredASns();
|
||||
};
|
||||
|
||||
filter = (message) => {
|
||||
return message.type === 'announcement';
|
||||
};
|
||||
|
||||
squashAlerts = (alerts) => {
|
||||
const peers = [...new Set(alerts.map(alert => alert.matchedMessage.peer))].length;
|
||||
|
||||
if (peers >= this.thresholdMinPeers) {
|
||||
return alerts[0].message;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
monitor = (message) =>
|
||||
new Promise((resolve, reject) => {
|
||||
|
||||
const messageOrigin = message.originAS;
|
||||
const messagePrefix = message.prefix;
|
||||
const matchedRule = this.monitored.filter(i => message.path.getLast().includes(i.asn))[0];
|
||||
|
||||
if (matchedRule) {
|
||||
|
||||
const matchedPrefixRule = this.getMoreSpecificMatch(messagePrefix);
|
||||
if (!matchedPrefixRule) {
|
||||
const text = `${messageOrigin} is announcing ${messagePrefix} but this prefix is not in the configured list of announced prefixes`;
|
||||
|
||||
this.publishAlert(messageOrigin.getId() + "-" + messagePrefix,
|
||||
text,
|
||||
messageOrigin.getId(),
|
||||
matchedRule,
|
||||
message,
|
||||
{});
|
||||
}
|
||||
}
|
||||
|
||||
resolve(true);
|
||||
});
|
||||
|
||||
}
|
@@ -37,9 +37,10 @@ export default class MonitorHijack extends Monitor {
|
||||
constructor(name, channel, params, env){
|
||||
super(name, channel, params, env);
|
||||
this.thresholdMinPeers = (params && params.thresholdMinPeers != null) ? params.thresholdMinPeers : 2;
|
||||
this.updateMonitoredResources();
|
||||
};
|
||||
|
||||
updateMonitoredPrefixes = () => {
|
||||
updateMonitoredResources = () => {
|
||||
this.monitored = this.input.getMonitoredPrefixes();
|
||||
};
|
||||
|
||||
|
@@ -37,10 +37,10 @@ export default class MonitorNewPrefix extends Monitor {
|
||||
constructor(name, channel, params, env){
|
||||
super(name, channel, params, env);
|
||||
this.thresholdMinPeers = (params && params.thresholdMinPeers != null) ? params.thresholdMinPeers : 2;
|
||||
this.updateMonitoredPrefixes();
|
||||
this.updateMonitoredResources();
|
||||
};
|
||||
|
||||
updateMonitoredPrefixes = () => {
|
||||
updateMonitoredResources = () => {
|
||||
this.monitored = this.input.getMonitoredMoreSpecifics();
|
||||
};
|
||||
|
||||
|
@@ -37,10 +37,10 @@ export default class MonitorPath extends Monitor {
|
||||
constructor(name, channel, params, env){
|
||||
super(name, channel, params, env);
|
||||
this.thresholdMinPeers = (params && params.thresholdMinPeers != null) ? params.thresholdMinPeers : 0;
|
||||
this.updateMonitoredPrefixes();
|
||||
this.updateMonitoredResources();
|
||||
};
|
||||
|
||||
updateMonitoredPrefixes = () => {
|
||||
updateMonitoredResources = () => {
|
||||
this.monitored = this.input.getMonitoredPrefixes();
|
||||
};
|
||||
|
||||
|
@@ -44,10 +44,10 @@ export default class MonitorVisibility extends Monitor {
|
||||
});
|
||||
this.thresholdMinPeers = params.threshold;
|
||||
}
|
||||
this.updateMonitoredPrefixes();
|
||||
this.updateMonitoredResources();
|
||||
};
|
||||
|
||||
updateMonitoredPrefixes = () => {
|
||||
updateMonitoredResources = () => {
|
||||
this.monitored = this.input.getMonitoredPrefixes();
|
||||
this.monitoredSimpleArray = this.monitored.map(item => item.prefix);
|
||||
};
|
||||
|
Reference in New Issue
Block a user