1
0
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:
Massimo Candela
2019-11-28 15:47:42 +01:00
parent abb311cac6
commit f5996819ad
14 changed files with 244 additions and 53 deletions

View File

@@ -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:

View File

@@ -15,3 +15,11 @@
asn: 15562
ignoreMorespecifics: false
ignore: false
options:
monitorASns:
2914:
group: default
3333:
group: default

View File

@@ -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);
}
}
};

View File

@@ -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;
}

View File

@@ -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
});
}
};

View File

@@ -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');
};
}

View File

@@ -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;
};
}

View File

@@ -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;

View File

@@ -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
View 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);
});
}

View File

@@ -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();
};

View File

@@ -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();
};

View File

@@ -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();
};

View File

@@ -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);
};