├── debian ├── docs ├── source │ ├── format │ └── options ├── node-bgpalerter.dirs ├── node-bgpalerter.tmpfiles ├── node-bgpalerter.sysusers ├── conffiles ├── gbp.conf ├── watch ├── node-bgpalerter.install ├── node-bgpalerter.service ├── node-bgpalerter.postinst ├── changelog ├── rules ├── control └── copyright ├── tests ├── dump_tests │ ├── vrps.json │ ├── groups.test.yml │ ├── prefixes.test.yml │ ├── config.test.yml │ └── tests.js ├── groups.test.yml ├── groups.test.after.yml ├── neighbor_tests │ ├── groups.test.yml │ ├── prefixes.test.yml │ └── config.test.yml ├── rpki_tests │ ├── vrp.missing.json │ ├── vrp.wrong.json │ ├── roas.after.json │ ├── vrp.correct.json │ ├── roas.before.json │ ├── config.rpki.test.default.yml │ ├── config.rpki.test.api.yml │ ├── config.rpki.test.external.yml │ ├── config.rpki.test.external-roas.yml │ ├── tests.external-missing-roas.js │ ├── tests.api.js │ ├── tests.default.js │ └── tests.external.js ├── generate_tests │ ├── prefixes.initial.append.yml │ ├── prefixes.final.default.yml │ ├── prefixes.final.group.yml │ ├── prefixes.final.append.yml │ └── config.test.yml ├── .cache_clone │ ├── status-software-update.json │ ├── status-path-matching.json │ ├── status-withdrawal-detection.json │ ├── status-basic-hijack-detection.json │ └── status-prefix-detection.json ├── kafka_tests │ ├── config.kafka.test.yml │ └── testReportKafka.js ├── npm_tests │ └── testNpmLib.js ├── prefixes.test.yml ├── config.test.yml ├── 3_uptimemonitor.js ├── 4_groups.js └── reports_tests │ ├── testReportSyslog.js │ ├── config.reports.test.yml │ └── testsReportHttp.js ├── src ├── reports │ ├── email_templates │ │ ├── software-update.txt │ │ ├── roa.txt │ │ ├── rpki.txt │ │ ├── misconfiguration.txt │ │ ├── path.txt │ │ ├── visibility.txt │ │ ├── newprefix.txt │ │ └── hijack.txt │ ├── reportSlack.js │ ├── reportTelegram.js │ ├── reportWebex.js │ ├── reportFile.js │ ├── reportMatrix.js │ ├── reportKafka.js │ ├── reportSyslog.js │ ├── reportPullAPI.js │ └── reportAlerta.js ├── utils │ ├── pubSub.js │ ├── axiosEnrich.js │ ├── lossyBuffer.js │ ├── storage.js │ ├── rpkiDiffingTool.js │ ├── storages │ │ └── storageFile.js │ └── restApi.js ├── monitors │ ├── monitorPassthrough.js │ ├── monitorSwUpdates.js │ ├── monitorNewPrefix.js │ ├── monitorVisibility.js │ ├── monitorHijack.js │ └── monitorPathNeighbors.js ├── processMonitors │ ├── sentryModule.js │ ├── uptimeApi.js │ ├── uptimeHealthcheck.js │ └── uptime.js ├── connectors │ └── connectorSwUpdates.js ├── config │ └── configYml.js ├── consumer.js └── model.js ├── docs ├── img │ ├── diagram_release.png │ ├── bgpalerter_github_image.png │ └── diagram_release.drawio ├── datasets.md ├── node.md ├── update.md ├── ris-disconnections.md ├── develop.md ├── context.md ├── friends.md ├── path-neighbors.md ├── path-matching.md └── process-monitors.md ├── Dockerfile ├── .babelrc ├── groups.yml.example ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support_request.md └── workflows │ ├── release.yml │ ├── stale.yml │ └── main.yml ├── .npmignore ├── .gitignore ├── prefixes.yml.example ├── AUTHORS ├── LICENSE └── package.json /debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /tests/dump_tests/vrps.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/node-bgpalerter.dirs: -------------------------------------------------------------------------------- 1 | /etc/bgpalerter 2 | -------------------------------------------------------------------------------- /src/reports/email_templates/software-update.txt: -------------------------------------------------------------------------------- 1 | ${summary} -------------------------------------------------------------------------------- /debian/source/options: -------------------------------------------------------------------------------- 1 | single-debian-patch 2 | auto-commit 3 | -------------------------------------------------------------------------------- /tests/groups.test.yml: -------------------------------------------------------------------------------- 1 | reportFile: 2 | test: 3 | - filename -------------------------------------------------------------------------------- /debian/node-bgpalerter.tmpfiles: -------------------------------------------------------------------------------- 1 | d /run/bgpalerter - bgpalerter - - - 2 | -------------------------------------------------------------------------------- /debian/node-bgpalerter.sysusers: -------------------------------------------------------------------------------- 1 | u bgpalerter - "BGPalerter daemon user" 2 | -------------------------------------------------------------------------------- /tests/dump_tests/groups.test.yml: -------------------------------------------------------------------------------- 1 | reportFile: 2 | test: 3 | - filename -------------------------------------------------------------------------------- /tests/groups.test.after.yml: -------------------------------------------------------------------------------- 1 | reportFile: 2 | test: 3 | - filename-after -------------------------------------------------------------------------------- /tests/neighbor_tests/groups.test.yml: -------------------------------------------------------------------------------- 1 | reportFile: 2 | test: 3 | - filename -------------------------------------------------------------------------------- /debian/conffiles: -------------------------------------------------------------------------------- 1 | /etc/bgpalerter/config.yml 2 | /etc/bgpalerter/prefixes.yml 3 | -------------------------------------------------------------------------------- /debian/gbp.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | filter = [ '.gitignore', '.travis.yml', '.git*' ] 3 | -------------------------------------------------------------------------------- /docs/img/diagram_release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttgin/BGPalerter/HEAD/docs/img/diagram_release.png -------------------------------------------------------------------------------- /docs/img/bgpalerter_github_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttgin/BGPalerter/HEAD/docs/img/bgpalerter_github_image.png -------------------------------------------------------------------------------- /tests/dump_tests/prefixes.test.yml: -------------------------------------------------------------------------------- 1 | 193.0.20.0/23: 2 | description: No description provided 3 | asn: 4 | - 1234 5 | ignoreMorespecifics: false 6 | ignore: false 7 | group: noc -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=4 2 | opts=\ 3 | dversionmangle=auto,\ 4 | filenamemangle=s/.*\/v?([\d\.-]+)\.tar\.gz/node-bgpalerter-$1.tar.gz/ \ 5 | https://github.com/nttgin/BGPalerter/releases .*/archive.*/v?([\d\.]+).tar.gz 6 | -------------------------------------------------------------------------------- /src/reports/email_templates/roa.txt: -------------------------------------------------------------------------------- 1 | ${summary} 2 | 3 | DETAILS: 4 | ------------------------------------------------------ 5 | Event type: ${type} 6 | When event started: ${earliest} UTC 7 | Last event: ${latest} UTC -------------------------------------------------------------------------------- /src/reports/email_templates/rpki.txt: -------------------------------------------------------------------------------- 1 | ${summary} 2 | 3 | DETAILS: 4 | ------------------------------------------------------ 5 | Event type: ${type} 6 | When event started: ${earliest} UTC 7 | Last event: ${latest} UTC 8 | See more: ${rpkiLink} -------------------------------------------------------------------------------- /tests/rpki_tests/vrp.missing.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "asn": "13335", 4 | "prefix": "103.21.244.0/24", 5 | "maxLength": 24 6 | }, 7 | { 8 | "asn": "AS2914", 9 | "prefix": "8.8.8.0/22", 10 | "maxLength": 24 11 | } 12 | ] -------------------------------------------------------------------------------- /tests/rpki_tests/vrp.wrong.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "asn": "13335", 4 | "prefix": "103.21.244.0/24", 5 | "maxLength": 24 6 | }, 7 | { 8 | "asn": "2914", 9 | "prefix": "123.4.5.0/14", 10 | "maxLength": 24 11 | } 12 | ] -------------------------------------------------------------------------------- /tests/generate_tests/prefixes.initial.append.yml: -------------------------------------------------------------------------------- 1 | 1.1.1.1/23: 2 | description: No description provided 3 | asn: 4 | - 2222 5 | ignoreMorespecifics: false 6 | ignore: false 7 | group: default 8 | options: 9 | monitorASns: 10 | '2222': 11 | group: default -------------------------------------------------------------------------------- /tests/.cache_clone/status-software-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": 1593370429793, 3 | "value": { 4 | "alerts": {}, 5 | "sent": { 6 | "software-update": 1593370424789 7 | }, 8 | "truncated": {}, 9 | "fadeOff": {} 10 | } 11 | } -------------------------------------------------------------------------------- /debian/node-bgpalerter.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dh-exec 2 | config.yml.example => etc/bgpalerter/config.yml 3 | prefixes.yml.example => etc/bgpalerter/prefixes.yml 4 | # required until sysuser support is added to debhelper-compat (expected in 14) 5 | debian/node-bgpalerter.sysusers => usr/lib/sysusers.d/node-bgpalerter.conf 6 | -------------------------------------------------------------------------------- /src/reports/email_templates/misconfiguration.txt: -------------------------------------------------------------------------------- 1 | ${summary} 2 | 3 | DETAILS: 4 | ------------------------------------------------------ 5 | Event type: ${type} 6 | When event started: ${earliest} UTC 7 | Last event: ${latest} UTC 8 | 9 | Top ${pathNumber} most used AS paths: 10 | ${paths} -------------------------------------------------------------------------------- /docs/datasets.md: -------------------------------------------------------------------------------- 1 | # List of datasets used by BGPalerter 2 | 3 | * [RIPE RIS live](https://ris-live.ripe.net/) 4 | * [RIPEstat](http://stat.ripe.net/) 5 | * [NTT RPKI VRPs](https://rpki.gin.ntt.net/api/export.json) 6 | * [Cloudflare VRPs](https://rpki.cloudflare.com/) 7 | * [rpki-client.org VRPs](https://www.rpki-client.org/) -------------------------------------------------------------------------------- /src/reports/email_templates/path.txt: -------------------------------------------------------------------------------- 1 | ${summary} 2 | 3 | 4 | DETAILS: 5 | ------------------------------------------------------ 6 | Event type: ${type} 7 | When event started: ${earliest} UTC 8 | Last event: ${latest} UTC 9 | 10 | 11 | Top ${pathNumber} triggering AS paths: 12 | ${paths} 13 | -------------------------------------------------------------------------------- /tests/.cache_clone/status-path-matching.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": 1593370452107, 3 | "value": { 4 | "alerts": {}, 5 | "sent": { 6 | "98.5.4.3/22-1": 1593370447102, 7 | "99.5.4.3/22-0": 1593370447103 8 | }, 9 | "truncated": {}, 10 | "fadeOff": {} 11 | } 12 | } -------------------------------------------------------------------------------- /debian/node-bgpalerter.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=BGPalerter 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | Restart=on-failure 8 | User=bgpalerter 9 | WorkingDirectory=/run/bgpalerter 10 | ExecStart=/usr/bin/bgpalerter --config /etc/bgpalerter/config.yml 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # -- trivial container for BGPalerter 2 | FROM node:18.19.0-alpine as build 3 | 4 | WORKDIR /opt/bgpalerter 5 | COPY . . 6 | 7 | # Makes the final image respect /etc/timezone configuration 8 | RUN apk add --no-cache tzdata 9 | 10 | RUN npm ci --no-audit --prefer-offline 11 | 12 | ENTRYPOINT ["npm"] 13 | CMD ["run", "serve"] 14 | -------------------------------------------------------------------------------- /tests/rpki_tests/roas.after.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "asn": "2914", 4 | "prefix": "1.2.3.0/24", 5 | "maxLength": 24, 6 | "ta": "ripe", 7 | "expires": 1621691602 8 | }, 9 | { 10 | "asn": 2914, 11 | "prefix": "2001:db8:123::/48", 12 | "maxLength": 48, 13 | "ta": "ripe" 14 | } 15 | ] -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "plugins": [ 6 | "@babel/plugin-transform-async-to-generator" 7 | ], 8 | "ignore": [ 9 | "./node_modules", 10 | "./assets", 11 | "./view", 12 | "./tests", 13 | "./logs", 14 | "./dist", 15 | "./build" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /groups.yml.example: -------------------------------------------------------------------------------- 1 | # This file is an example of how you can define user groups 2 | 3 | # The structure is: 4 | # report_module_name: 5 | # user_group_to_define: 6 | # list_of_contacts 7 | 8 | # The format of the list of contacts depends on the report_module (e.g., emails for reportEmail, urls for reportHTTP) 9 | 10 | reportEmail: 11 | mygroup: 12 | - example@example.it -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | target-branch: dev 9 | - package-ecosystem: docker 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | open-pull-requests-limit: 10 14 | target-branch: dev 15 | -------------------------------------------------------------------------------- /tests/.cache_clone/status-withdrawal-detection.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": 1593370433850, 3 | "value": { 4 | "alerts": {}, 5 | "sent": { 6 | "165.254.225.0/24": 1593370428845, 7 | "2a00:5884::/32": 1593370428846, 8 | "2001:db8:123::/48": 1593370428847 9 | }, 10 | "truncated": {}, 11 | "fadeOff": {} 12 | } 13 | } -------------------------------------------------------------------------------- /tests/rpki_tests/vrp.correct.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "asn": "13335", 4 | "prefix": "103.21.244.0/24", 5 | "maxLength": 24 6 | }, 7 | { 8 | "asn": "AS2914", 9 | "prefix": "8.8.8.0/22", 10 | "maxLength": 24 11 | }, 12 | { 13 | "asn": 1234, 14 | "prefix": "82.112.100.0/24", 15 | "maxLength": 24 16 | } 17 | ] -------------------------------------------------------------------------------- /tests/.cache_clone/status-basic-hijack-detection.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": 1593370439943, 3 | "value": { 4 | "alerts": {}, 5 | "sent": { 6 | "15562-4-165.254.255.0/25": 1593370434937, 7 | "208585-2a00:5884:ffff::/48": 1593370434938, 8 | "15563-2a00:5884::/32": 1593370434939 9 | }, 10 | "truncated": {}, 11 | "fadeOff": {} 12 | } 13 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | config.yml 2 | prefixes.yml 3 | groups.yml 4 | .idea/ 5 | node_modules/ 6 | bin/ 7 | build/ 8 | src/ 9 | logs/ 10 | .DS_Store 11 | bgpalerter.pid 12 | alertdata/ 13 | .npmrc 14 | .cache/ 15 | volumetests/ 16 | tests/ 17 | Dockerfile 18 | .travis.yml 19 | docs/ 20 | prefixes.yml.example 21 | config.yml.example 22 | build.sh 23 | index.js 24 | .github/ 25 | .hound.yml 26 | .eslintrc.json 27 | .babelrc 28 | debian/ 29 | -------------------------------------------------------------------------------- /src/reports/email_templates/visibility.txt: -------------------------------------------------------------------------------- 1 | ${summary} 2 | 3 | 4 | DETAILS: 5 | ------------------------------------------------------ 6 | Monitored prefix: ${prefix} 7 | Prefix Description: ${description} 8 | Prefix origin: ${asn} 9 | Event type: ${type} 10 | When event started: ${earliest} UTC 11 | Last event: ${latest} UTC 12 | Detected by peers: ${peers} 13 | See in BGPlay: ${bgplay} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.yml 2 | prefixes.yml 3 | groups.yml 4 | .idea/ 5 | node_modules/ 6 | bin/ 7 | dist/ 8 | build/ 9 | logs/ 10 | .DS_Store 11 | bgpalerter.pid 12 | alertdata/ 13 | .npmrc 14 | .cache/ 15 | volumetests/ 16 | tests/rpki_tests/vrp.json 17 | tests/rpki_tests/roas.json 18 | tests/generate_tests/prefixes.yml 19 | export.json 20 | tests/kafka_tests/kafka_2.13-2.6.0/ 21 | tests/kafka_tests/kafka_2.13-2.6.0.tgz 22 | tests/kafka_tests/nohup.out 23 | *.sw? 24 | -------------------------------------------------------------------------------- /tests/.cache_clone/status-prefix-detection.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": 1593370446017, 3 | "value": { 4 | "alerts": {}, 5 | "sent": { 6 | "15562-165.254.255.0/25": 1593370441013, 7 | "204092-2a00:5884:ffff::/48": 1593370441014, 8 | "1234-175.254.205.0/25": 1593370441016, 9 | "1234-170.254.205.0/25": 1593370441016 10 | }, 11 | "truncated": {}, 12 | "fadeOff": {} 13 | } 14 | } -------------------------------------------------------------------------------- /debian/node-bgpalerter.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # The postinst file is required until sysuser support is added to debhelper-compat (expected in 14) 3 | set -e 4 | 5 | case "$1" in 6 | configure) 7 | systemd-sysusers 8 | ;; 9 | 10 | abort-upgrade|abort-remove|abort-deconfigure) 11 | : 12 | ;; 13 | 14 | *) 15 | echo "postinst called with unknown argument \`$1'" >&2 16 | exit 1 17 | ;; 18 | esac 19 | 20 | #DEBHELPER# 21 | 22 | exit 0 23 | -------------------------------------------------------------------------------- /tests/neighbor_tests/prefixes.test.yml: -------------------------------------------------------------------------------- 1 | 9.5.4.3/22: 2 | description: test 3 | asn: 101 4 | ignoreMorespecifics: false 5 | 6 | 99.5.4.3/22: 7 | description: test 8 | asn: 101 9 | ignoreMorespecifics: false 10 | 11 | options: 12 | monitorASns: 13 | 101: 14 | group: default 15 | upstreams: 16 | - 100 17 | downstreams: 18 | - 104 19 | 80: 20 | group: default 21 | upstreams: 22 | - 99 23 | downstreams: 24 | -------------------------------------------------------------------------------- /src/reports/email_templates/newprefix.txt: -------------------------------------------------------------------------------- 1 | ${summary} 2 | 3 | 4 | DETAILS: 5 | ------------------------------------------------------ 6 | Monitored prefix: ${prefix} 7 | Prefix Description: ${description} 8 | Usually announced by: ${asn} 9 | Event type: ${type} 10 | Detected new prefix: ${newprefix} 11 | Announced by: ${neworigin} 12 | When event started: ${earliest} UTC 13 | Last event: ${latest} UTC 14 | Detected by peers: ${peers} 15 | See in BGPlay: ${bgplay} 16 | 17 | -------------------------------------------------------------------------------- /src/reports/email_templates/hijack.txt: -------------------------------------------------------------------------------- 1 | ${summary} 2 | 3 | 4 | DETAILS: 5 | ------------------------------------------------------ 6 | Monitored prefix: ${prefix} 7 | Prefix Description: ${description} 8 | Usually announced by: ${asn} 9 | Event type: ${type} 10 | Now announced by: ${neworigin} 11 | Now announced with: ${newprefix} 12 | When event started: ${earliest} UTC 13 | Last event: ${latest} UTC 14 | Detected by peers: ${peers} 15 | See in BGPlay: ${bgplay} 16 | 17 | Top ${pathNumber} most used AS paths: 18 | ${paths} -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | node-bgpalerter (1.33.0-1) unstable; urgency=medium 2 | 3 | * Deb release of BGPalerter 1.33.0 4 | 5 | -- Massimo Candela Sun, 24 Dec 2023 16:19:06 +0100 6 | 7 | node-bgpalerter (1.31.1-1) unstable; urgency=medium 8 | 9 | * Don't strip packages.json 10 | * inject a shebang top the bin script 11 | 12 | -- John Bond Fri, 06 Jan 2023 15:39:06 +0100 13 | 14 | node-bgpalerter (1.29.0-1) unstable; urgency=medium 15 | 16 | * Initial release 17 | 18 | -- John Bond Fri, 14 Jan 2022 13:43:04 +0100 19 | -------------------------------------------------------------------------------- /docs/node.md: -------------------------------------------------------------------------------- 1 | # Installing Node.js 2 | 3 | Some fast commands are below. The complete documentation (including other platforms) is [here](https://nodejs.org/en/download/) 4 | 5 | ## With apt (e.g., debian) 6 | 7 | ```bash 8 | curl -sL https://deb.nodesource.com/setup_18.x | sudo bash - 9 | sudo apt install nodejs 10 | ``` 11 | 12 | 13 | ## With homebrew (e.g., macos) 14 | 15 | ```bash 16 | brew update 17 | brew install node@18 18 | ``` 19 | 20 | ## With yum (e.g., centos) 21 | 22 | ```bash 23 | curl -sL https://deb.nodesource.com/setup_18.x | sudo bash - 24 | sudo yum install nodejs 25 | ``` 26 | -------------------------------------------------------------------------------- /prefixes.yml.example: -------------------------------------------------------------------------------- 1 | 165.254.225.0/24: 2 | description: a description about this prefix 3 | asn: 15562 4 | ignoreMorespecifics: false 5 | ignore: false 6 | 7 | 165.254.255.0/24: 8 | description: the description will be reported in the alerts 9 | asn: 15562 10 | ignoreMorespecifics: false 11 | ignore: false 12 | 13 | 192.147.168.0/24: 14 | description: descriptions make alerts easier to read 15 | asn: 15562 16 | ignoreMorespecifics: false 17 | ignore: false 18 | 19 | 20 | options: 21 | monitorASns: 22 | 2914: 23 | group: default 24 | 3333: 25 | group: default -------------------------------------------------------------------------------- /src/utils/pubSub.js: -------------------------------------------------------------------------------- 1 | export default class PubSub { 2 | constructor() { 3 | this.callbacks = {}; 4 | }; 5 | 6 | subscribe(channel, callback) { 7 | this.callbacks[channel] = this.callbacks[channel] || []; 8 | this.callbacks[channel].push(callback); 9 | }; 10 | 11 | publish(channel, content) { 12 | for (let clb of this.callbacks[channel] || []) { 13 | new Promise(function (resolve, reject) { 14 | clb(content, channel); 15 | resolve(true); 16 | }) 17 | .catch(console.log); 18 | } 19 | }; 20 | 21 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Provide an example** 14 | Provide an example in terms of prefixes and BGP messages. Possibly provide a snippet of config.yml and prefixes.yml. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Are you using the binary or the source code?** 20 | 21 | **Your information** 22 | Provide your name and your AS/company (see Bert Hubert's post https://berthub.eu/articles/posts/anonymous-help/). 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe what you would like to achieve** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe why the current solution (if any) is not satisfactory** 14 | A clear and concise description. 15 | 16 | **Provide an example** 17 | Provide an example in terms of prefixes and BGP messages. Possibly provide a snippet of config.yml and prefixes.yml. 18 | 19 | **Your information** 20 | Provide your name and your AS/company (see Bert Hubert's post https://berthub.eu/articles/posts/anonymous-help/). 21 | -------------------------------------------------------------------------------- /tests/rpki_tests/roas.before.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "asn": "2914", 4 | "prefix": "1.2.3.0/24", 5 | "maxLength": 24, 6 | "ta": "ripe" 7 | }, 8 | { 9 | "asn": "AS2914", 10 | "prefix": "2.3.4.0/24", 11 | "maxLength": 24, 12 | "ta": "ripe" 13 | }, 14 | { 15 | "asn": 2914, 16 | "prefix": "2001:db8:123::/48", 17 | "maxLength": 48, 18 | "ta": "ripe" 19 | }, 20 | { 21 | "asn": 65000, 22 | "prefix": "2001:db8:123::/48", 23 | "maxLength": 48, 24 | "ta": "ripe" 25 | }, 26 | { 27 | "asn": 2914, 28 | "prefix": "94.5.4.3/22", 29 | "maxLength": 22, 30 | "ta": "ripe" 31 | } 32 | ] -------------------------------------------------------------------------------- /docs/update.md: -------------------------------------------------------------------------------- 1 | # Updating BGPalerter 2 | 3 | BGPalerter can be easily updated. The configuration of BGPalerter is persisted in `config.yml` and `prefixes.yml`, as long as you preserve such files you will not need to reconfigure it after the update. 4 | 5 | * If you are using the binary, go in the [releases tab](https://github.com/nttgin/BGPalerter/releases), download the new binary and replace the old one. 6 | * If you are using the source code, simply do a git pull of the main branch. After, do `npm install` to update the dependencies. 7 | * If you are using docker, do `docker pull nttgin/bgpalerter:latest`, after stop and remove your current container and [run it again](installation.md#running-bgpalerter-in-docker). 8 | * You can set [automatic updates](linux-service.md#automatic-updates). -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | BGPalerter was originally created in February 2019 at NTT Ltd. 2 | 3 | Here is a list of authors and contributors who patched or extended the code. 4 | If this list is not up to date, please contact NTT or one of the authors. 5 | 6 | - AUTHORS - 7 | 8 | Massimo Candela 9 | NTT 10 | https://massimocandela.com/ 11 | 12 | - CONTRIBUTORS - 13 | Damian Zaremba, Fastly 14 | Mircea Ulinic, DigitalOcean 15 | Alan Haynes, Harbin Clinic 16 | Florian Domain, Criteo 17 | Louis Poinsignon, Cloudflare 18 | See complete list at https://github.com/nttgin/BGPalerter/graphs/contributors 19 | 20 | Special thanks to: 21 | Job Snijders for OpenBSD rpki-client (https://www.rpki-client.org/) 22 | RIPE NCC for the RIS live service (https://ris-live.ripe.net/). 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support request 3 | about: Ask support 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **BGPalerter is offered for free, as is. No support is offered.** 11 | 12 | **If you are looking for a service that does everything for you and includes support, use https://packetvis.com** 13 | 14 | **Did you read the documentation? Then ask the community for help.** 15 | 16 | ### In your request, provide an answer to all the following questions: 17 | 18 | * A clear and concise description of what the issue is. 19 | * Point to the part of the documentation you tried to use to address the issue. 20 | * Provide the steps to reproduce the issue 21 | * Provide the logs you are getting 22 | * Provide the version you are using, and if it is the binary or the source code. 23 | * Provide your name and your AS/company 24 | -------------------------------------------------------------------------------- /docs/ris-disconnections.md: -------------------------------------------------------------------------------- 1 | # RIPE RIS disconnections 2 | 3 | Sometimes among the error logs you will find RIS disconnection logs (usually error 1006). 4 | 5 | The following causes are possible: 6 | 7 | 1) **Network issues.** The machine where BGPalerter is running loses connectivity (maybe just for a few seconds). 8 | 2) **You are monitoring something that produces too many BGP updates** (e.g., your prefixes are not stable or constantly re-announced). In such cases you may be too slow in consuming the data and the server disconnects you to flush the buffer. 9 | 3) **Process termination.** This happens when BGPalerter was killed or crashed for some reason, this is not related to RIPE RIS. 10 | 11 | Anyway, unfortunately sometimes this happens without an explanation due to RIPE RIS instabilities. 12 | This has been reported to the RIPE RIS team. 13 | 14 | _Please, send an email at rislive@ripe.net for inquiries or to highlight the importance of the reliability of this service._ 15 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | 6 | jobs: 7 | build-debs: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Set up Javascript/Node 11 | uses: actions/setup-node@v4 12 | with: 13 | node-version: 22.14.0 14 | 15 | - name: Check out code 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: '0' 19 | 20 | - name: Build 21 | run: | 22 | npm install 23 | npm run build 24 | 25 | - uses: jtdor/build-deb-action@v1 26 | env: 27 | DEB_BUILD_OPTIONS: noautodbgsym 28 | with: 29 | buildpackage-opts: --build=binary --no-sign 30 | extra-build-deps: git 31 | - uses: "marvinpinto/action-automatic-releases@latest" 32 | with: 33 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 34 | prerelease: true 35 | files: | 36 | LICENSE 37 | bin/* 38 | debian/artifacts/*.deb 39 | -------------------------------------------------------------------------------- /tests/generate_tests/prefixes.final.default.yml: -------------------------------------------------------------------------------- 1 | 193.0.10.0/23: 2 | description: No description provided 3 | asn: 4 | - 3333 5 | ignoreMorespecifics: false 6 | ignore: false 7 | group: noc 8 | 193.0.20.0/23: 9 | description: No description provided 10 | asn: 11 | - 3333 12 | ignoreMorespecifics: false 13 | ignore: false 14 | group: noc 15 | 193.0.0.0/21: 16 | description: No description provided 17 | asn: 18 | - 3333 19 | ignoreMorespecifics: false 20 | ignore: false 21 | group: noc 22 | 193.0.12.0/23: 23 | description: No description provided 24 | asn: 25 | - 3333 26 | ignoreMorespecifics: false 27 | ignore: false 28 | group: noc 29 | 193.0.18.0/23: 30 | description: No description provided 31 | asn: 32 | - 3333 33 | ignoreMorespecifics: false 34 | ignore: false 35 | group: noc 36 | 193.0.22.0/23: 37 | description: No description provided 38 | asn: 39 | - 3333 40 | ignoreMorespecifics: false 41 | ignore: false 42 | group: noc 43 | options: 44 | monitorASns: 45 | '3333': 46 | group: noc 47 | -------------------------------------------------------------------------------- /docs/develop.md: -------------------------------------------------------------------------------- 1 | # More information for developers 2 | 3 | To start development see how to install the source [here](installation.md#running-bgpalerter-from-the-source-code). 4 | 5 | 6 | ## All npm commands 7 | 8 | * `npm run serve` to run the application from the source code 9 | 10 | * `npm run test` to run the tests 11 | 12 | * `npm run inspect` to run the application with the inspect flag, which allows profiling in Chrome (chrome://inspect). 13 | 14 | * `npm run build` to compile and build OS native applications 15 | 16 | * `npm run generate-prefixes -- --a ASN(S) --o OUTPUT_FILE` to generate the monitored prefixes file 17 | 18 | ## Composition notes 19 | 20 | You can compose the tool with 3 main components: connectors, monitors, and reports. 21 | 22 | > **Important:** 23 | All connectors MUST extend the class Connector. Monitors extend the class Monitor. Reports extend the class Report. 24 | From the superclass they will inherit various generic methods while some have to be implemented. 25 | 26 | Reports don't receive only alerts but also the data that provoked such alerts (so you can store the data and replay the accident later). 27 | -------------------------------------------------------------------------------- /src/utils/axiosEnrich.js: -------------------------------------------------------------------------------- 1 | import md5 from "md5"; 2 | 3 | const attempts = {}; 4 | const numAttempts = 2; 5 | 6 | const retry = function (axios, error, params) { 7 | return new Promise((resolve, reject) => { 8 | setTimeout(() => { 9 | const key = md5(JSON.stringify(params)); 10 | attempts[key] = attempts[key] || 0; 11 | attempts[key]++; 12 | if (attempts[key] <= numAttempts) { 13 | resolve(axios.request(params)); 14 | } else { 15 | reject(error); 16 | } 17 | }, 2000); 18 | }); 19 | }; 20 | 21 | export default function (axios, userAgent) { 22 | 23 | axios.defaults ??= {}; 24 | axios.defaults.headers ??= {}; 25 | axios.defaults.headers.common ??= {}; 26 | 27 | if (userAgent) { 28 | axios.defaults.headers.common = { 29 | "User-Agent": userAgent 30 | }; 31 | } 32 | 33 | axios.defaults.headers.common = { 34 | ...axios.defaults.headers.common, 35 | "Accept-Encoding": "gzip" 36 | }; 37 | 38 | return params => axios(params) 39 | .catch(error => retry(axios, error, params)); 40 | } -------------------------------------------------------------------------------- /src/utils/lossyBuffer.js: -------------------------------------------------------------------------------- 1 | export default class LossyBuffer { 2 | 3 | constructor(bufferSize, cleaningInterval, logger) { 4 | this.callback = null; 5 | this.buffer = []; 6 | this.bufferSize = bufferSize; 7 | setInterval(this.sendData, cleaningInterval); 8 | this.alertOnce = false; 9 | this.logger = logger; 10 | }; 11 | 12 | sendData = () => { 13 | if (this.callback && this.buffer.length) { 14 | this.callback(this.buffer); 15 | this.buffer = []; 16 | } 17 | }; 18 | 19 | add = (item) => { 20 | if (this.buffer.length <= this.bufferSize) { 21 | this.buffer.push(item); 22 | 23 | } else if (!this.alertOnce) { 24 | this.alertOnce = true; 25 | this.logger.log({ 26 | level: "error", 27 | message: "The data rate is too high, messages are getting dropped due to full buffer. Increase the buffer size if you think your machine could handle more." 28 | }); 29 | } 30 | 31 | }; 32 | 33 | onData = (callback) => { 34 | this.callback = callback; 35 | }; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /docs/img/diagram_release.drawio: -------------------------------------------------------------------------------- 1 | 5VfbctMwEP2aPGbGl9z6SNJQCqVDb4Q3RtgbW0TWprIcJ/161pFsx3Ua2jKFAg+JraO11nv27Eru+JNkfaLYMv6IIYiO54Trjn/c8TzXHXl0KZCNQYa+BSLFQ2tUA1f8DizoWDTjIaQNQ40oNF82wQClhEA3MKYU5k2zOYqm1yWLoAVcBUy00RkPdWzQkTes8XfAo7j07A6OzEzCSmMbSRqzEPMdyJ92/IlC1OYuWU9AFOSVvMxONzNxthicvL9Ib9nN+MP1+eeuWeztUx6pQlAg9bOXPs1v/PFl/3YxVV9HF1+ibO2uun0bmt6UfEFI9NkhKh1jhJKJaY2OFWYyhGJVh0a1zRnikkCXwO+g9cZqgWUaCYp1Iuys8Vk4upein8Rn7VLMVAAH7HwrM6Yi0AeCd6skkvoBE9BqQ88pEEzzVfPlmJVhVNnVVNONZfsJSbUvuWIis54uQQBLoagFJkMeMg2t7NTcF0TmMddwtWRbNnKq3ybPc5TaJsGlQMeRYGlq05ZqhYuqIgrrSt5OlaLHZmQFSsN6h682qXa2Z2vJNpNqnNel6Zb1Fu+UZWn3K2nYq4HBX14ADwv7EQXg/aYCOPSSOwXwSUFXlUXwR4XfEvRjk/Og8P1+75Up33u4Af1j3A+Gr63rDFvcUwROl3694rUAFildcV4cQ1ByjYrLqER0XOwR59fX9C9B56gWhT9piGJCbE9wOafQSuOlghXHLDXFbXeZOapir4khWJjVU82+ccE1xTfZWtpG4zmZJvyOmgJKM0cb1PbMlpBH8zBZZ0KnLfFQLnVTIUYFExTk3z+WKIueOudC3IOY4JGkYUC5B8LHhTI4neze2ImEh+G2Ie+TZLNJv5S03KN70hrtkVZvj7SOXkpao33SGltV7RWVUY7CMAtMgg/I7L9Kbt95ZnKf0TdoWH9JbOd2vsf86Q8= -------------------------------------------------------------------------------- /tests/generate_tests/prefixes.final.group.yml: -------------------------------------------------------------------------------- 1 | 193.0.10.0/23: 2 | description: No description provided 3 | asn: 4 | - 3333 5 | ignoreMorespecifics: false 6 | ignore: false 7 | group: test 8 | 193.0.20.0/23: 9 | description: No description provided 10 | asn: 11 | - 3333 12 | ignoreMorespecifics: false 13 | ignore: false 14 | group: test 15 | 193.0.0.0/21: 16 | description: No description provided 17 | asn: 18 | - 3333 19 | ignoreMorespecifics: false 20 | ignore: false 21 | group: test 22 | 193.0.12.0/23: 23 | description: No description provided 24 | asn: 25 | - 3333 26 | ignoreMorespecifics: false 27 | ignore: false 28 | group: test 29 | 193.0.18.0/23: 30 | description: No description provided 31 | asn: 32 | - 3333 33 | ignoreMorespecifics: false 34 | ignore: false 35 | group: test 36 | 193.0.22.0/23: 37 | description: No description provided 38 | asn: 39 | - 3333 40 | ignoreMorespecifics: false 41 | ignore: false 42 | group: test 43 | '2001:67c:2e8::/48': 44 | description: No description provided 45 | asn: 46 | - 3333 47 | ignoreMorespecifics: false 48 | ignore: false 49 | group: test 50 | options: 51 | monitorASns: 52 | '3333': 53 | group: test 54 | -------------------------------------------------------------------------------- /src/monitors/monitorPassthrough.js: -------------------------------------------------------------------------------- 1 | import Monitor from "./monitor"; 2 | 3 | export default class monitorPassthrough extends Monitor { 4 | 5 | constructor(name, channel, params, env, input) { 6 | super(name, channel, params, env, input); 7 | this.count = 0; 8 | }; 9 | 10 | updateMonitoredResources = () => { 11 | // nothing 12 | }; 13 | 14 | filter = () => { 15 | return true; 16 | }; 17 | 18 | squashAlerts = (alerts) => { 19 | const item = alerts[0].matchedMessage; 20 | return `type:${item.type} timestamp:${alerts[0].timestamp} prefix:${item.prefix} peer:${item.peer} path:${item.path} nextHop:${item.nextHop} aggregator:${item.aggregator}`; 21 | }; 22 | 23 | monitor = (message) => 24 | new Promise((resolve, reject) => { 25 | const prefix = message.prefix; 26 | this.publishAlert(this.count, 27 | prefix, 28 | { 29 | prefix: "0.0.0.0/0", 30 | asn: "1234", 31 | description: "test" 32 | }, 33 | message, 34 | {}); 35 | 36 | this.count++; 37 | 38 | resolve(true); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | export DH_VERBOSE = 1 3 | %: 4 | dh $@ --with nodejs 5 | 6 | override_dh_auto_build: 7 | babeljs index.js -d "${CURDIR}/build" 8 | babeljs src -d "${CURDIR}/build/src" 9 | cp package.json "${CURDIR}/build/" 10 | cd "${CURDIR}/build" && npm pack 11 | 12 | override_dh_auto_test: 13 | /bin/true 14 | 15 | override_dh_auto_install: 16 | npm install --prefix "${CURDIR}/debian/node-bgpalerter/usr" -g ${CURDIR}/build/bgpalerter-*.tgz 17 | find "${CURDIR}/debian/node-bgpalerter/usr" \ 18 | \( -name .npmignore -o -name .eslintrc -o -name .eslintrc.js -o -name \*.md -o -name LICENSE \ 19 | -o -name LICENSE-jsbn -o -name .gitmodules -o -name .gitattributes \) \ 20 | -type f -delete 21 | find "${CURDIR}/debian/node-bgpalerter/usr/lib/node_modules/bgpalerter/node_modules" \ 22 | \( -name examples -name .bin -o -name bin \) -type d -exec rm -rf {} + 23 | 24 | override_dh_link: 25 | # We dont want to link the vendored packages 26 | /bin/true 27 | 28 | override_dh_fixperms: 29 | dh_fixperms 30 | sed -i '1i #!/usr/bin/node' "${CURDIR}/debian/node-bgpalerter/usr/lib/node_modules/bgpalerter/index.js" 31 | chmod +x "${CURDIR}/debian/node-bgpalerter/usr/lib/node_modules/bgpalerter/index.js" 32 | 33 | override_dh_auto_clean: 34 | rm -rf "${CURDIR}/build" 35 | -------------------------------------------------------------------------------- /tests/rpki_tests/config.rpki.test.default.yml: -------------------------------------------------------------------------------- 1 | environment: test 2 | 3 | connectors: 4 | - file: connectorTest 5 | name: tes 6 | params: 7 | testType: withdrawal 8 | 9 | monitors: 10 | - file: monitorRPKI 11 | channel: rpki 12 | name: rpki-monitor 13 | params: 14 | thresholdMinPeers: 1 15 | checkUncovered: true 16 | cacheValidPrefixesSeconds: 3600 17 | 18 | 19 | # The file containing the monitored prefixes. Please see monitored_prefixes_test.yml for an example 20 | # This is an array (use new lines and dashes!) 21 | monitoredPrefixesFiles: 22 | - tests/prefixes.test.yml 23 | 24 | logging: 25 | directory: logs 26 | logRotatePattern: YYYY-MM-DD # Whenever the pattern changes, a new file is created and the old one rotated 27 | maxRetainedFiles: 10 28 | maxFileSizeMB: 15 29 | compressOnRotation: true 30 | 31 | checkForUpdatesAtBoot: false 32 | persistStatus: false 33 | 34 | rpki: 35 | refreshVrpListMinutes: 15 36 | 37 | notificationIntervalSeconds: 1800 # Repeat the same alert (which keeps being triggered) after x seconds 38 | alertOnlyOnce: false 39 | fadeOffSeconds: 10 40 | checkFadeOffGroupsSeconds: 2 41 | pidFile: bgpalerter.pid 42 | multiProcess: false 43 | maxMessagesPerSecond: 6000 44 | configVersion: 3 -------------------------------------------------------------------------------- /tests/rpki_tests/config.rpki.test.api.yml: -------------------------------------------------------------------------------- 1 | environment: test 2 | 3 | connectors: 4 | - file: connectorTest 5 | name: tes 6 | params: 7 | testType: withdrawal 8 | 9 | monitors: 10 | - file: monitorRPKI 11 | channel: rpki 12 | name: rpki-monitor 13 | params: 14 | thresholdMinPeers: 1 15 | checkUncovered: true 16 | cacheValidPrefixesSeconds: 3600 17 | 18 | 19 | # The file containing the monitored prefixes. Please see monitored_prefixes_test.yml for an example 20 | # This is an array (use new lines and dashes!) 21 | monitoredPrefixesFiles: 22 | - tests/prefixes.test.yml 23 | 24 | logging: 25 | directory: logs 26 | logRotatePattern: YYYY-MM-DD # Whenever the pattern changes, a new file is created and the old one rotated 27 | maxRetainedFiles: 10 28 | maxFileSizeMB: 15 29 | compressOnRotation: true 30 | 31 | checkForUpdatesAtBoot: false 32 | persistStatus: false 33 | 34 | rpki: 35 | vrpProvider: api 36 | url: https://rpki.gin.ntt.net/api/export.json 37 | 38 | notificationIntervalSeconds: 1800 # Repeat the same alert (which keeps being triggered) after x seconds 39 | alertOnlyOnce: false 40 | fadeOffSeconds: 10 41 | checkFadeOffGroupsSeconds: 2 42 | pidFile: bgpalerter.pid 43 | multiProcess: false 44 | maxMessagesPerSecond: 6000 45 | configVersion: 3 -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: node-bgpalerter 2 | Section: javascript 3 | Priority: optional 4 | Maintainer: John Bond 5 | Testsuite: autopkgtest-pkg-nodejs 6 | Build-Depends: 7 | debhelper-compat (= 13) 8 | , pkg-js-tools 9 | , dh-exec 10 | , nodejs (>= 6) 11 | , node-babel7 12 | , npm 13 | Standards-Version: 4.6.0 14 | Homepage: https://github.com/nttgin/BGPalerter#readme 15 | 16 | Package: node-bgpalerter 17 | Architecture: all 18 | Depends: 19 | ${misc:Depends} 20 | , nodejs (>= 6) 21 | Description: Self-configuring BGP monitoring tool, which allows you to monitor in real-time if: 22 | any of your prefixes loses visibility; 23 | any of your prefixes is hijacked; 24 | your AS is announcing RPKI invalid prefixes (e.g., not matching prefix length); 25 | your AS is announcing prefixes not covered by ROAs; 26 | any of your ROAs is expiring; 27 | ROAs covering your prefixes are no longer reachable; 28 | RPKI Trust Anchors malfunctions; 29 | a ROA involving any of your prefixes or ASes was deleted/added/edited; 30 | your AS is announcing a new prefix that was never announced before; 31 | an unexpected upstream (left-side) AS appears in an AS path; 32 | an unexpected downstream (right-side) AS appears in an AS path; 33 | one of the AS paths used to reach your prefix matches a specific condition defined by you. 34 | -------------------------------------------------------------------------------- /tests/rpki_tests/config.rpki.test.external.yml: -------------------------------------------------------------------------------- 1 | environment: test 2 | 3 | connectors: 4 | - file: connectorTest 5 | name: tes 6 | params: 7 | testType: withdrawal 8 | 9 | monitors: 10 | - file: monitorRPKI 11 | channel: roa 12 | name: roa-monitor 13 | params: 14 | checkDisappearing: true 15 | thresholdMinPeers: 1 16 | checkUncovered: false 17 | cacheValidPrefixesSeconds: 0 18 | 19 | 20 | # The file containing the monitored prefixes. Please see monitored_prefixes_test.yml for an example 21 | # This is an array (use new lines and dashes!) 22 | monitoredPrefixesFiles: 23 | - tests/prefixes.test.yml 24 | 25 | logging: 26 | directory: logs 27 | logRotatePattern: YYYY-MM-DD # Whenever the pattern changes, a new file is created and the old one rotated 28 | maxRetainedFiles: 10 29 | maxFileSizeMB: 15 30 | compressOnRotation: true 31 | 32 | checkForUpdatesAtBoot: false 33 | persistStatus: false 34 | 35 | rpki: 36 | refreshVrpListMinutes: 15 37 | vrpFile: tests/rpki_tests/vrp.json 38 | 39 | 40 | notificationIntervalSeconds: 1800 # Repeat the same alert (which keeps being triggered) after x seconds 41 | alertOnlyOnce: false 42 | fadeOffSeconds: 1 43 | checkFadeOffGroupsSeconds: 1 44 | pidFile: bgpalerter.pid 45 | multiProcess: false 46 | maxMessagesPerSecond: 6000 47 | configVersion: 3 -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | export default class Storage { 2 | 3 | constructor(params, config) { 4 | this.config = config; 5 | this.params = params; 6 | this.validity = (this.params.validitySeconds ? (this.params.validitySeconds * 1000) : null) || Infinity; 7 | }; 8 | 9 | set = (key, value) => { 10 | if (/^[A-Za-z0-9\-_]+$/i.test(key)) { 11 | const envelop = { 12 | date: new Date().getTime(), 13 | value 14 | }; 15 | 16 | return this._set(key, envelop); 17 | } else { 18 | 19 | return Promise.reject("Not a valid key. Use only chars and dashes."); 20 | } 21 | }; 22 | 23 | get = (key) => { 24 | return this._get(key) 25 | .then((data) => { 26 | if (!!data) { 27 | const {date, value} = data; 28 | const now = new Date().getTime(); 29 | if (date + this.validity >= now) { 30 | return value; 31 | } 32 | } 33 | return {}; 34 | }); 35 | }; 36 | 37 | _set = (key, value) => { 38 | throw new Error("The set method must be implemented"); 39 | }; 40 | 41 | _get = (key) => { 42 | throw new Error("The get method must be implemented"); 43 | }; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /tests/generate_tests/prefixes.final.append.yml: -------------------------------------------------------------------------------- 1 | 1.1.1.1/23: 2 | description: No description provided 3 | asn: 4 | - 2222 5 | ignoreMorespecifics: false 6 | ignore: false 7 | group: default 8 | 193.0.18.0/23: 9 | description: No description provided 10 | asn: 11 | - 3333 12 | ignoreMorespecifics: false 13 | ignore: false 14 | group: test 15 | '2001:67c:2e8::/48': 16 | description: No description provided 17 | asn: 18 | - 3333 19 | ignoreMorespecifics: false 20 | ignore: false 21 | group: test 22 | 193.0.10.0/23: 23 | description: No description provided 24 | asn: 25 | - 3333 26 | ignoreMorespecifics: false 27 | ignore: false 28 | group: test 29 | 193.0.20.0/23: 30 | description: No description provided 31 | asn: 32 | - 3333 33 | ignoreMorespecifics: false 34 | ignore: false 35 | group: test 36 | 193.0.22.0/23: 37 | description: No description provided 38 | asn: 39 | - 3333 40 | ignoreMorespecifics: false 41 | ignore: false 42 | group: test 43 | 193.0.0.0/21: 44 | description: No description provided 45 | asn: 46 | - 3333 47 | ignoreMorespecifics: false 48 | ignore: false 49 | group: test 50 | 193.0.12.0/23: 51 | description: No description provided 52 | asn: 53 | - 3333 54 | ignoreMorespecifics: false 55 | ignore: false 56 | group: test 57 | options: 58 | monitorASns: 59 | '2222': 60 | group: default 61 | '3333': 62 | group: test -------------------------------------------------------------------------------- /src/utils/rpkiDiffingTool.js: -------------------------------------------------------------------------------- 1 | function getPrefixes(vrp, asn) { 2 | return [...new Set(vrp.filter(i => i.asn === asn).map(i => i.prefix))]; 3 | } 4 | 5 | function getRelevant(vrp, prefixes, asns = []) { 6 | return vrp.filter(i => asns.includes(i.asn) || prefixes.includes(i.prefix)); 7 | } 8 | 9 | function diff(vrpsOld, vrpsNew, asn, prefixesIn = []) { 10 | asn = parseInt(asn); 11 | 12 | let prefixes; 13 | if (asn) { 14 | prefixes = [...new Set(prefixesIn)]; 15 | } else { 16 | prefixes = [...new Set([...prefixesIn, ...getPrefixes(vrpsOld, asn), ...getPrefixes(vrpsNew, asn)])]; 17 | } 18 | const filteredVrpsOld = JSON.parse(JSON.stringify(getRelevant(vrpsOld, prefixes, [asn]))) 19 | .map(i => { 20 | i.status = "removed"; 21 | return i; 22 | }); 23 | const filteredVrpsNew = JSON.parse(JSON.stringify(getRelevant(vrpsNew, prefixes, [asn]))) 24 | .map(i => { 25 | i.status = "added"; 26 | return i; 27 | }); 28 | 29 | const index = {}; 30 | 31 | for (let vrp of filteredVrpsOld.concat(filteredVrpsNew)) { 32 | const key = `${vrp.ta}-${vrp.prefix}-${vrp.asn}-${vrp.maxLength}`; 33 | index[key] = index[key] || []; 34 | index[key].push(vrp); 35 | } 36 | 37 | return Object.values(index).filter(i => i.length === 1).map(i => i[0]); 38 | } 39 | 40 | export { 41 | getPrefixes, 42 | getRelevant, 43 | diff 44 | }; -------------------------------------------------------------------------------- /tests/dump_tests/config.test.yml: -------------------------------------------------------------------------------- 1 | environment: test 2 | 3 | connectors: 4 | - file: connectorRISDump 5 | name: dmp 6 | 7 | monitors: 8 | - file: monitorHijack 9 | channel: hijack 10 | name: basic-hijack-detection 11 | params: 12 | thresholdMinPeers: 0 13 | 14 | reports: 15 | - file: reportFile 16 | channels: 17 | - hijack 18 | - newprefix 19 | - visibility 20 | - path 21 | - misconfiguration 22 | - rpki 23 | params: 24 | persistAlertData: false 25 | alertDataDirectory: alertdata/ 26 | 27 | 28 | # The file containing the monitored prefixes. Please see monitored_prefixes_test.yml for an example 29 | # This is an array (use new lines and dashes!) 30 | monitoredPrefixesFiles: 31 | - prefixes.test.yml 32 | 33 | logging: 34 | directory: logs 35 | logRotatePattern: YYYY-MM-DD # Whenever the pattern changes, a new file is created and the old one rotated 36 | maxRetainedFiles: 10 37 | maxFileSizeMB: 15 38 | compressOnRotation: true 39 | 40 | checkForUpdatesAtBoot: true 41 | persistStatus: true 42 | 43 | volume: volumetests/ 44 | 45 | 46 | rpki: 47 | refreshVrpListMinutes: 15 48 | vrpFile: tests/dump_tests/vrps.json 49 | 50 | notificationIntervalSeconds: 1800 # Repeat the same alert (which keeps being triggered) after x seconds 51 | alertOnlyOnce: false 52 | fadeOffSeconds: 10 53 | checkFadeOffGroupsSeconds: 2 54 | pidFile: bgpalerter.pid 55 | multiProcess: false 56 | maxMessagesPerSecond: 6000 57 | configVersion: 3 -------------------------------------------------------------------------------- /tests/rpki_tests/config.rpki.test.external-roas.yml: -------------------------------------------------------------------------------- 1 | environment: test 2 | 3 | connectors: 4 | - file: connectorTest 5 | name: tes 6 | params: 7 | testType: withdrawal 8 | 9 | monitors: 10 | - file: monitorROAS 11 | channel: roa 12 | name: roa-monitor 13 | params: 14 | enableDiffAlerts: true 15 | enableExpirationAlerts: true 16 | enableExpirationCheckTA: true 17 | enableDeletedCheckTA: true 18 | enableAdvancedRpkiStats: false 19 | roaExpirationAlertHours: 2 20 | checkOnlyASns: false 21 | toleranceDeletedRoasTA: 20 22 | toleranceExpiredRoasTA: 20 23 | 24 | 25 | # The file containing the monitored prefixes. Please see monitored_prefixes_test.yml for an example 26 | # This is an array (use new lines and dashes!) 27 | monitoredPrefixesFiles: 28 | - tests/prefixes.test.yml 29 | 30 | logging: 31 | directory: logs 32 | logRotatePattern: YYYY-MM-DD # Whenever the pattern changes, a new file is created and the old one rotated 33 | maxRetainedFiles: 10 34 | maxFileSizeMB: 15 35 | compressOnRotation: true 36 | 37 | checkForUpdatesAtBoot: false 38 | persistStatus: false 39 | 40 | rpki: 41 | refreshVrpListMinutes: 15 42 | vrpFile: tests/rpki_tests/roas.json 43 | 44 | 45 | notificationIntervalSeconds: 1800 # Repeat the same alert (which keeps being triggered) after x seconds 46 | alertOnlyOnce: false 47 | fadeOffSeconds: 10 48 | checkFadeOffGroupsSeconds: 2 49 | pidFile: bgpalerter.pid 50 | multiProcess: false 51 | maxMessagesPerSecond: 6000 -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Stale 7 | 8 | on: 9 | workflow_dispatch: 10 | schedule: 11 | - cron: '0 13 * * *' 12 | 13 | jobs: 14 | stale: 15 | 16 | runs-on: ubuntu-latest 17 | permissions: 18 | issues: write 19 | pull-requests: write 20 | 21 | steps: 22 | - uses: actions/stale@v9 23 | with: 24 | days-before-issue-stale: 90 25 | days-before-pr-stale: 90 26 | days-before-issue-close: 0 27 | days-before-pr-close: 0 28 | repo-token: ${{ secrets.GITHUB_TOKEN }} 29 | exempt-issue-labels: > 30 | no-stale, 31 | pinned 32 | close-issue-message: > 33 | This issue has been automatically closed as stale. 34 | This mechanism helps to prioritize feature requests which received more support from the community. 35 | If you want to open again this issue you have to provide a Pull Request. 36 | close-pr-message: > 37 | This pr has been automatically closed as stale. 38 | This mechanism helps to prioritize feature requests which received more support from the community. 39 | A pr may become stale because there is no interest or bandwidth in reviewing/merging it. 40 | stale-issue-label: 'wontfix' 41 | stale-pr-label: 'wontfix' 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, NTT Ltd. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /docs/context.md: -------------------------------------------------------------------------------- 1 | # Report context 2 | 3 | All the report modules inherit the method `getContext` from the super class `Report`. This method returns a dictionary with some pre-computed tags useful for composing textual reports. 4 | Such tags are reported in the table below. 5 | 6 | | Tag | Description | 7 | |---|---| 8 | | summary | The summary of the alert | 9 | | earliest | The date of the earliest event that triggered the alert (format YYYY-MM-DD HH:mm:ss)| 10 | | latest | The date of the last event that triggered the alert (format YYYY-MM-DD HH:mm:ss)| 11 | | channel | The channel where the alert is coming from | 12 | | type | The name of the monitor that triggered the alert | 13 | | prefix | The monitored prefix involved in the alert | 14 | | description | The description of the prefix involved in the alert | 15 | | asn | The monitored AS involved in the alert | 16 | | paths | The AS Paths involved in the alert | 17 | | pathNumber | The count of AS Paths in the alert | 18 | | peers | The number of peers that were able to see the issue | 19 | | neworigin | The AS announcing the monitored prefix (e.g., in case of a hijack, `neworigin` will contain the hijacker, `asn` will contain the usual origin) | 20 | | newprefix | The prefix announced (e.g., in case of a hijack, `newprefix` will contain the more specific prefix used for the hijack, `prefix` will contain the usual prefix) | 21 | | bgplay | A link to BGPlay | 22 | |rpkiLink| A link to the rpki validator online| 23 | 24 | Usage example: `The alert involves ${prefix} in ${earliest}` will be translated in something like `The alert involves 1.2.3.4/24 in 2020-04-14 04:02:13`. 25 | 26 | > The same approach must be used to populate the templates available in config.yml. If you are writing a template for an API call, convert the JSON to string (e.g., '{"text": "${summary}"}'). -------------------------------------------------------------------------------- /src/utils/storages/storageFile.js: -------------------------------------------------------------------------------- 1 | import Storage from "../storage"; 2 | import fs from "fs"; 3 | 4 | export default class StorageFile extends Storage { 5 | constructor(params, config) { 6 | super(params, config); 7 | this.directory = this.config.volume + (this.params.directory || ".cache/"); 8 | this.enabled = true; 9 | try { 10 | if (!fs.existsSync(this.directory)) { 11 | fs.mkdirSync(this.directory, {recursive: true}); 12 | } 13 | } catch (error) { 14 | this.enabled = false; 15 | } 16 | }; 17 | 18 | _set = (key, value) => 19 | new Promise((resolve, reject) => { 20 | if (this.enabled) { 21 | const file = this.directory + key + ".json"; 22 | try { 23 | fs.writeFileSync(file, JSON.stringify(value)); 24 | resolve(true); 25 | } catch (error) { 26 | reject(error); 27 | } 28 | } else { 29 | reject("The .cache/ directory is not writeable"); 30 | } 31 | }); 32 | 33 | _get = (key) => 34 | new Promise((resolve, reject) => { 35 | if (this.enabled) { 36 | const file = this.directory + key + ".json"; 37 | try { 38 | if (fs.existsSync(file)) { 39 | resolve(JSON.parse(fs.readFileSync(file, "utf8"))); 40 | } else { 41 | resolve(null); 42 | } 43 | } catch (error) { 44 | reject(error); 45 | } 46 | } else { 47 | reject("The .cache/ directory is not writeable"); 48 | } 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/restApi.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | export default class RestApi { 4 | static _instance; 5 | 6 | constructor(params) { 7 | 8 | this.params = params; 9 | this.port = this.params.port || 8011; 10 | this.host = this.params.host || null; 11 | this.enabled = false; 12 | this.urls = {}; 13 | this._serverPromise = null; 14 | 15 | if (!!RestApi._instance) { 16 | return RestApi._instance; 17 | } 18 | 19 | RestApi._instance = this; 20 | } 21 | 22 | _startServer = () => { 23 | if (!this._serverPromise) { 24 | this._serverPromise = new Promise((resolve, reject) => { 25 | try { 26 | if (this.port) { 27 | this.server = express(); 28 | this.server.listen(this.port, this.host || undefined, () => { 29 | this.enabled = true; 30 | resolve(); 31 | }); 32 | } else { 33 | this.enabled = false; 34 | reject("The port parameter must be specified to start the REST API."); 35 | } 36 | } catch (error) { 37 | this.enabled = false; 38 | reject(error); 39 | } 40 | }); 41 | } 42 | 43 | return this._serverPromise; 44 | }; 45 | 46 | addUrl = (url, callback) => { 47 | if (this.urls[url]) { 48 | return Promise.reject("The URL for the REST API already exists and cannot be replaced"); 49 | } else { 50 | this.urls[url] = callback; 51 | return this._startServer() 52 | .then(() => { 53 | this.server.get(url, this.urls[url]); 54 | }); 55 | } 56 | }; 57 | } -------------------------------------------------------------------------------- /tests/generate_tests/config.test.yml: -------------------------------------------------------------------------------- 1 | environment: test 2 | 3 | connectors: 4 | - file: connectorTest 5 | name: tes 6 | params: 7 | testType: withdrawal 8 | 9 | monitors: 10 | - file: monitorHijack 11 | channel: hijack 12 | name: basic-hijack-detection 13 | params: 14 | thresholdMinPeers: 0 15 | 16 | - file: monitorNewPrefix 17 | channel: newprefix 18 | name: prefix-detection 19 | params: 20 | thresholdMinPeers: 0 21 | 22 | - file: monitorVisibility 23 | channel: visibility 24 | name: withdrawal-detection 25 | params: 26 | thresholdMinPeers: 4 27 | 28 | - file: monitorPath 29 | channel: path 30 | name: path-matching 31 | params: 32 | thresholdMinPeers: 0 33 | 34 | - file: monitorAS 35 | channel: misconfiguration 36 | name: asn-monitor 37 | params: 38 | thresholdMinPeers: 2 39 | 40 | - file: monitorRPKI 41 | channel: rpki 42 | name: rpki-monitor 43 | params: 44 | thresholdMinPeers: 1 45 | checkUncovered: true 46 | 47 | 48 | reports: 49 | 50 | # The file containing the monitored prefixes. Please see monitored_prefixes_test.yml for an example 51 | # This is an array (use new lines and dashes!) 52 | monitoredPrefixesFiles: 53 | - prefixes.yml 54 | 55 | logging: 56 | directory: logs 57 | logRotatePattern: YYYY-MM-DD # Whenever the pattern changes, a new file is created and the old one rotated 58 | maxRetainedFiles: 10 59 | maxFileSizeMB: 15 60 | compressOnRotation: true 61 | 62 | checkForUpdatesAtBoot: false 63 | 64 | notificationIntervalSeconds: 1800 # Repeat the same alert (which keeps being triggered) after x seconds 65 | alertOnlyOnce: false 66 | fadeOffSeconds: 10 67 | checkFadeOffGroupsSeconds: 2 68 | pidFile: bgpalerter.pid 69 | multiProcess: false 70 | maxMessagesPerSecond: 6000 71 | configVersion: 3 -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: bgpalerter 3 | Upstream-Contact: https://github.com/nttgin/BGPalerter/issues 4 | Source: https://github.com/nttgin/BGPalerter#readme 5 | 6 | Files: * 7 | Copyright: 2022, Massimo Candela (https://massimocandela.com) 8 | License: BSD-3-Clause 9 | 10 | Files: debian/* 11 | Copyright: 2022, John Bond 12 | License: BSD-3-Clause 13 | 14 | License: BSD-3-Clause 15 | Redistribution and use in source and binary forms, with or without 16 | modification, are permitted provided that the following conditions 17 | are met: 18 | 1. Redistributions of source code must retain the above copyright 19 | notice, this list of conditions and the following disclaimer. 20 | 2. Redistributions in binary form must reproduce the above copyright 21 | notice, this list of conditions and the following disclaimer in the 22 | documentation and/or other materials provided with the distribution. 23 | 3. Neither the name of the University nor the names of its contributors 24 | may be used to endorse or promote products derived from this software 25 | without specific prior written permission. 26 | . 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 28 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR 31 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 32 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 33 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 34 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 35 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 36 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 37 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 38 | 39 | -------------------------------------------------------------------------------- /src/processMonitors/sentryModule.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import * as Sentry from "@sentry/node"; 34 | 35 | export default class SentryModule { 36 | 37 | constructor(connectors, params) { 38 | 39 | if (params.dsn) { 40 | Sentry.init({dsn: params.dsn}); 41 | } 42 | 43 | }; 44 | 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/friends.md: -------------------------------------------------------------------------------- 1 | # Who is using BGPalerter 2 | 3 | Please, let me know so I can add your company name here. 4 | 5 | * Seattle Internet Exchange (SIX) 6 | * Food and Agriculture Organization of the United Nations (FAO) 7 | * Latin America and Caribbean Network Information Centre (LACNIC) 8 | * DigitalOcean (DO) 9 | * Freethought Internet (AS41000) 10 | * MTLNOG 11 | * EBOX (AS1403) 12 | * Cloudflare (AS13335) 13 | * Vocus (AS4826, AS9443) 14 | * HEAnet (AS1213) 15 | * Tech Futures (AS394256) 16 | * Fastly (AS54113) 17 | * EDGOO Networks (AS47787) 18 | * IT.Gate (AS12779) 19 | * Sky (AS5607) 20 | * SBTAP (AS59715) 21 | * WiscNet (AS2381) 22 | * Artfiles GmbH (AS8893) 23 | * Namex IXP (AS24796) 24 | * Zero Attack Vector LLC (AS212996, AS398549) 25 | * Accuris Technologies Ltd. (AS212934) 26 | * Brennercom S.p.A. (AS20811) 27 | * Solcon Internetdiensten B.V. (AS12414) 28 | * Webair (AS27257) 29 | * Genesis Cloud (AS209045) 30 | * Suretec Systems Limited T/A SureVoIP (AS199659) 31 | * TelcoSwitch Limited (AS45033) 32 | * Ziron Limited (AS49344) 33 | * Elite Limited (AS29611) 34 | * Scaleway (AS12876) 35 | * NTS workspace (AS15576) 36 | * Axera S.P.A. (AS34758) 37 | * GoDaddy (AS26496, AS20773) 38 | * SWITCH (AS559) 39 | * Artfiles GmbH (AS8893) 40 | * CENGN (AS395262) 41 | * Freie Netze München e.V. (AS212567) 42 | * TDC NET (AS3292) 43 | * Stuttgart-IX (AS41139, AS212522) 44 | * Globalways GmbH (AS48918) 45 | * TOP-IX (AS41364, AS209631) 46 | * MontanaSky Networks (AS18897) 47 | * FPTCLOUD (AS140766) 48 | * Wikimedia Foundation (AS14907) 49 | * SIDN (AS1140) 50 | * Bluebird Network (AS62943) 51 | * GalaxyGate (AS397031) 52 | * The George Washington University (AS11039) 53 | * NL-ix (AS34307) 54 | * LINX (AS5459) 55 | * Rechenzentrum Haßfurt GmbH (AS44973) 56 | * obe.net (AS3399) 57 | * IT-Total (AS8769) 58 | * Speakup (AS49627) 59 | * Nick Bouwhuis (AS202585) 60 | * IX.br (AS26162, AS263044, AS20121) 61 | * Cyberfusion (AS204983) 62 | * EscapeNet (AS7600) 63 | * Productsup GmbH (AS200249) 64 | * QuxLabs (AS203038, AS214503) 65 | * Leapswitch Networks (AS132335) 66 | * Tier 4 Cloud Services (AS146943) 67 | * TNGNET (AS39521) -------------------------------------------------------------------------------- /tests/kafka_tests/config.kafka.test.yml: -------------------------------------------------------------------------------- 1 | environment: test 2 | 3 | connectors: 4 | - file: connectorTest 5 | name: tes 6 | params: 7 | testType: withdrawal 8 | 9 | monitors: 10 | - file: monitorHijack 11 | channel: hijack 12 | name: basic-hijack-detection 13 | params: 14 | thresholdMinPeers: 0 15 | 16 | - file: monitorNewPrefix 17 | channel: newprefix 18 | name: prefix-detection 19 | params: 20 | thresholdMinPeers: 0 21 | 22 | - file: monitorVisibility 23 | channel: visibility 24 | name: withdrawal-detection 25 | params: 26 | thresholdMinPeers: 4 27 | 28 | - file: monitorPath 29 | channel: path 30 | name: path-matching 31 | params: 32 | thresholdMinPeers: 0 33 | 34 | - file: monitorAS 35 | channel: misconfiguration 36 | name: asn-monitor 37 | params: 38 | thresholdMinPeers: 2 39 | 40 | # - file: monitorRPKI 41 | # channel: rpki 42 | # name: rpki-monitor 43 | # params: 44 | # thresholdMinPeers: 1 45 | # checkUncovered: true 46 | 47 | 48 | reports: 49 | - file: reportKafka 50 | channels: 51 | - hijack 52 | - newprefix 53 | - visibility 54 | - path 55 | - misconfiguration 56 | - rpki 57 | params: 58 | host: localhost 59 | port: 9092 60 | topics: 61 | default: bgpalerter 62 | 63 | # The file containing the monitored prefixes. Please see monitored_prefixes_test.yml for an example 64 | # This is an array (use new lines and dashes!) 65 | monitoredPrefixesFiles: 66 | - tests/prefixes.test.yml 67 | 68 | logging: 69 | directory: logs 70 | logRotatePattern: YYYY-MM-DD # Whenever the pattern changes, a new file is created and the old one rotated 71 | maxRetainedFiles: 10 72 | maxFileSizeMB: 15 73 | compressOnRotation: true 74 | 75 | checkForUpdatesAtBoot: false 76 | persistStatus: false 77 | 78 | 79 | notificationIntervalSeconds: 1800 # Repeat the same alert (which keeps being triggered) after x seconds 80 | alertOnlyOnce: false 81 | fadeOffSeconds: 10 82 | checkFadeOffGroupsSeconds: 2 83 | pidFile: bgpalerter.pid 84 | multiProcess: false 85 | maxMessagesPerSecond: 6000 86 | configVersion: 3 -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | pull_request: 7 | branches: [ "*" ] 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Set up Javascript/Node 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 22.14.0 19 | 20 | - name: Check out code 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: '0' 24 | 25 | - name: Cache multiple paths 26 | uses: actions/cache@v4 27 | with: 28 | path: ~/.npm 29 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 30 | restore-keys: | 31 | ${{ runner.os }}-node- 32 | 33 | - name: Build 34 | run: | 35 | npm install 36 | npm run compile 37 | 38 | test: 39 | name: Test 40 | runs-on: ubuntu-latest 41 | steps: 42 | 43 | - name: Set up nodejs 44 | uses: actions/setup-node@v4 45 | with: 46 | node-version: 22.14.0 47 | 48 | - name: Check out code 49 | uses: actions/checkout@v4 50 | with: 51 | fetch-depth: '0' 52 | 53 | - name: Cache multiple paths 54 | uses: actions/cache@v4 55 | with: 56 | path: ~/.npm 57 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 58 | restore-keys: | 59 | ${{ runner.os }}-node- 60 | 61 | - name: Install 62 | run: | 63 | npm install 64 | 65 | - name: Tests 66 | run: | 67 | npm run test-core 68 | npm run test-generate 69 | npm run test-reports 70 | npm run test-neighbor 71 | npm run test-dump 72 | 73 | - name: Tests RPKI 74 | run: | 75 | npm run test-rpki 76 | 77 | - name: Tests NPM 78 | run: | 79 | npm run test-npm 80 | 81 | - name: Tests Kafka 82 | run: | 83 | sudo apt-get -y install tar 84 | sudo apt-get -y install wget 85 | wget -O kafka.tgz https://archive.apache.org/dist/kafka/3.5.0/kafka_2.12-3.5.0.tgz 86 | mkdir kafka && tar -xzf kafka.tgz -C kafka --strip-components=1 87 | nohup ./kafka/bin/zookeeper-server-start.sh ./kafka/config/zookeeper.properties & 88 | nohup ./kafka/bin/kafka-server-start.sh ./kafka/config/server.properties & 89 | nohup ./kafka/bin/kafka-topics.sh --create --topic bgpalerter --bootstrap-server 0.0.0.0:9092 --replication-factor 1 --partitions 1 & 90 | sleep 30 && npm run test-kafka -------------------------------------------------------------------------------- /tests/neighbor_tests/config.test.yml: -------------------------------------------------------------------------------- 1 | environment: test 2 | 3 | connectors: 4 | - file: connectorTest 5 | name: tes 6 | params: 7 | testType: withdrawal 8 | 9 | monitors: 10 | - file: monitorHijack 11 | channel: hijack 12 | name: basic-hijack-detection 13 | params: 14 | thresholdMinPeers: 0 15 | 16 | - file: monitorNewPrefix 17 | channel: newprefix 18 | name: prefix-detection 19 | params: 20 | thresholdMinPeers: 0 21 | 22 | - file: monitorVisibility 23 | channel: visibility 24 | name: withdrawal-detection 25 | params: 26 | thresholdMinPeers: 4 27 | 28 | - file: monitorPath 29 | channel: path 30 | name: path-matching 31 | params: 32 | thresholdMinPeers: 0 33 | 34 | - file: monitorAS 35 | channel: misconfiguration 36 | name: asn-monitor 37 | params: 38 | thresholdMinPeers: 2 39 | 40 | - file: monitorRPKI 41 | channel: rpki 42 | name: rpki-monitor 43 | params: 44 | thresholdMinPeers: 1 45 | checkUncovered: true 46 | 47 | - file: monitorROAS 48 | channel: rpki 49 | name: rpki-monitor 50 | 51 | - file: monitorPathNeighbors 52 | channel: path-neighbors 53 | name: path-neighbors 54 | params: 55 | thresholdMinPeers: 0 56 | 57 | reports: 58 | - file: reportFile 59 | channels: 60 | - hijack 61 | - newprefix 62 | - visibility 63 | - path 64 | - misconfiguration 65 | - rpki 66 | params: 67 | persistAlertData: false 68 | alertDataDirectory: alertdata/ 69 | 70 | 71 | # The file containing the monitored prefixes. Please see monitored_prefixes_test.yml for an example 72 | # This is an array (use new lines and dashes!) 73 | monitoredPrefixesFiles: 74 | - prefixes.test.yml 75 | 76 | logging: 77 | directory: logs 78 | logRotatePattern: YYYY-MM-DD # Whenever the pattern changes, a new file is created and the old one rotated 79 | maxRetainedFiles: 10 80 | maxFileSizeMB: 15 81 | compressOnRotation: true 82 | 83 | checkForUpdatesAtBoot: true 84 | persistStatus: true 85 | 86 | volume: volumetests/ 87 | 88 | groupsFile: groups.test.yml 89 | 90 | rpki: 91 | vrpProvider: ntt 92 | refreshVrpListMinutes: 15 93 | markDataAsStaleAfterMinutes: 120 94 | 95 | notificationIntervalSeconds: 1800 # Repeat the same alert (which keeps being triggered) after x seconds 96 | alertOnlyOnce: false 97 | fadeOffSeconds: 10 98 | checkFadeOffGroupsSeconds: 2 99 | pidFile: bgpalerter.pid 100 | multiProcess: false 101 | maxMessagesPerSecond: 6000 102 | configVersion: 3 -------------------------------------------------------------------------------- /tests/npm_tests/testNpmLib.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | const chai = require("chai"); 34 | const chaiSubset = require("chai-subset"); 35 | chai.use(chaiSubset); 36 | const expect = chai.expect; 37 | const volume = "volumetests/"; 38 | const asyncTimeout = 20000; 39 | 40 | const Config = require("../../src/config/config").default; 41 | 42 | const ConfigTest = function () { 43 | 44 | this.retrieve = () => { 45 | const data = (new Config()).default; 46 | 47 | data.test = true; 48 | 49 | return data; 50 | }; 51 | 52 | this.save = () => { 53 | return true; 54 | }; 55 | }; 56 | 57 | describe("External Connector", function () { 58 | 59 | it("load external connector", function () { 60 | const Worker = require("../../src/worker").default; 61 | const worker = new Worker({volume, configConnector: ConfigTest}); 62 | const config = worker.config; 63 | expect(config.test).to.equal(true); 64 | }) 65 | .timeout(asyncTimeout); 66 | }); -------------------------------------------------------------------------------- /src/processMonitors/uptimeApi.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Uptime from "./uptime"; 34 | import env from "../env"; 35 | import RestApi from "../utils/restApi"; 36 | 37 | export default class UptimeApi extends Uptime { 38 | 39 | constructor(connectors, params) { 40 | super(connectors, params); 41 | this.server = null; 42 | this.connectors = connectors; 43 | let restDefault = env.config.rest || {port: params.port, host: params.host}; 44 | const rest = new RestApi(restDefault); 45 | 46 | rest.addUrl("/status", this.respond) 47 | .catch(error => { 48 | env.logger.log({ 49 | level: "error", 50 | message: error 51 | }); 52 | }); 53 | }; 54 | 55 | respond = (req, res, next) => { 56 | res.contentType = "json"; 57 | const response = this.getCurrentStatus(); 58 | if (this.params.useStatusCodes && response.warning) { 59 | res.status(500); 60 | } 61 | res.send(response); 62 | next(); 63 | }; 64 | 65 | } 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/monitors/monitorSwUpdates.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Monitor from "./monitor"; 34 | 35 | export default class MonitorSwUpdates extends Monitor { 36 | 37 | constructor(name, channel, params, env, input) { 38 | super(name, channel, params, env, input); 39 | }; 40 | 41 | updateMonitoredResources = () => { 42 | // nothing 43 | }; 44 | 45 | filter = (message) => { 46 | return message.type === "software-update"; 47 | }; 48 | 49 | squashAlerts = (alerts) => { 50 | const message = alerts[0].matchedMessage; 51 | 52 | return `A new version of BGPalerter is available. Current version: ${message.currentVersion} new version: ${message.newVersion}. Please, go to: ${message.repo}`; 53 | }; 54 | 55 | monitor = (message) => 56 | new Promise((resolve, reject) => { 57 | 58 | this.publishAlert("software-update", 59 | "bgpalerter", 60 | { 61 | group: "default" 62 | }, 63 | message, 64 | {}); 65 | 66 | resolve(true); 67 | }); 68 | 69 | } -------------------------------------------------------------------------------- /docs/path-neighbors.md: -------------------------------------------------------------------------------- 1 | # Upstream and downstream AS monitoring 2 | 3 | The component `monitorPathNeighbors` allows monitoring for unexpected neighbor ASes in AS paths. The list of neighbors can be specified in `prefixes.yml` inside the `monitorASns` sections. 4 | 5 | > For example, imagine AS100 has two upstreams, AS99 and AS98, and one downstream, AS101. You can express the following rule in 'prefixes.yml' 6 | > 7 | > ```yaml 8 | > options: 9 | > monitorASns: 10 | > 100: 11 | > group: noc 12 | > upstreams: 13 | > - 99 14 | > - 98 15 | > downstreams: 16 | > - 101 17 | > ``` 18 | 19 | Every time an AS path is detected with a different upstream/downstream AS, an alert will be generated. 20 | 21 | **You can generate the upstream/downstream lists automatically. Refer to the options `-u` and `-n` of the [auto configuration](prefixes.md#generate).** 22 | 23 | According to the above configuration, 24 | * the AS path [10, 20, 30, 100, 101] will generate an alert since AS30 is not an upstream of AS100; 25 | * the AS path [10, 20, 30, 100] will generate an alert since AS30 is not an upstream of AS100; 26 | * the AS path [10, 20, 99, 100, 101] will not generate an alert since AS99 is an upstream of AS100 and AS101 is a downstream of of AS100; 27 | * the AS path [10, 20, 99, 100, 104] will generate an alert since AS104 is not a downstream of AS100; 28 | * the AS path [100, 104] will generate an alert since AS104 is not a downstream of AS100. 29 | 30 | You can disable the monitoring by removing the upstreams and downstreams lists or by removing the `monitorPathNeighbors` block in `config.yml`. 31 | 32 | If you delete only one of the upstreams and downstreams lists, the monitoring will continue on the remaining one. 33 | 34 | > E.g., the config below monitors only for upstreams 35 | > 36 | > ```yaml 37 | > options: 38 | > monitorASns: 39 | > 100: 40 | > group: noc 41 | > upstreams: 42 | > - 99 43 | > - 98 44 | > ``` 45 | 46 | Example of alert: 47 | > A new upstream of AS100 has been detected: AS30 48 | 49 | 50 | If you provide empty lists, the monitoring will be performed and you will receive an alert for every upstream/downstream. 51 | 52 | > E.g., the config below monitors only for downstreams and expects to never see any downstream AS (stub network) 53 | > 54 | > ```yaml 55 | > options: 56 | > monitorASns: 57 | > 100: 58 | > group: noc 59 | > downstreams: 60 | > ``` 61 | 62 | 63 | 64 | Parameters for this monitor module: 65 | 66 | |Parameter| Description| 67 | |---|---| 68 | |thresholdMinPeers| Minimum number of peers that need to see the BGP update before to trigger an alert. | 69 | |maxDataSamples| Maximum number of collected BGP messages for each alert which doesn't reach yet the `thresholdMinPeers`. Default to 1000. As soon as the `thresholdMinPeers` is reached, the collected BGP messages are flushed, independently from the value of `maxDataSamples`.| 70 | -------------------------------------------------------------------------------- /tests/prefixes.test.yml: -------------------------------------------------------------------------------- 1 | 193.0.0.0/21: 2 | description: rpki valid not monitored AS 3 | asn: 1234 4 | ignoreMorespecifics: false 5 | 6 | 165.254.225.0/24: 7 | description: description 1 8 | asn: 15562 9 | ignoreMorespecifics: false 10 | 11 | 165.254.255.0/24: 12 | description: description 2 13 | asn: 15562 14 | ignoreMorespecifics: false 15 | group: groupName 16 | 17 | 192.147.168.0/24: 18 | description: description 3 19 | asn: 15562 20 | ignoreMorespecifics: true 21 | 22 | 2a00:5884::/32: 23 | description: alarig fix test 24 | asn: 25 | - 204092 26 | - 45 27 | ignoreMorespecifics: false 28 | 29 | 2a0e:f40::/29: 30 | description: alarig fix test 2 31 | asn: 208585 32 | ignoreMorespecifics: false 33 | 34 | 2a0e:f40::/30: 35 | description: ignore sub test 36 | asn: 1234 37 | ignoreMorespecifics: true 38 | 39 | 2a0e:240::/32: 40 | description: ignore flag test 41 | asn: 1234 42 | ignore: true 43 | ignoreMorespecifics: true 44 | 45 | 175.254.205.0/24: 46 | description: include exclude test 47 | asn: 1234 48 | ignoreMorespecifics: false 49 | ignore: false 50 | excludeMonitors: 51 | - basic-hijack-detection 52 | - withdrawal-detection 53 | - rpki-monitor 54 | 55 | 170.254.205.0/24: 56 | description: include exclude test 57 | asn: 1234 58 | ignoreMorespecifics: false 59 | ignore: false 60 | includeMonitors: 61 | - prefix-detection 62 | 63 | 94.5.4.3/22: 64 | description: path matching test only regex 65 | asn: 2914 66 | ignoreMorespecifics: false 67 | ignore: false 68 | path: 69 | match: ".*2914$" 70 | notMatch: ".*5060.*" 71 | matchDescription: test description 72 | 73 | 98.5.4.3/22: 74 | description: path matching test regex and maxLength 75 | asn: 2914 76 | ignoreMorespecifics: false 77 | ignore: false 78 | path: 79 | - match: ".*2915$" 80 | maxLength: 4 81 | matchDescription: test description1 82 | - match: ".*2914$" 83 | maxLength: 3 84 | matchDescription: test description2 85 | 86 | 99.5.4.3/22: 87 | description: path matching test regex and minLength 88 | asn: 2914 89 | ignoreMorespecifics: false 90 | ignore: false 91 | path: 92 | match: ".*2914$" 93 | minLength: 2 94 | matchDescription: test description 95 | 96 | 165.24.225.0/24: 97 | description: test fade off 98 | asn: 15562 99 | ignoreMorespecifics: false 100 | 101 | 2001:db8:123::/48: 102 | description: exact matching test 103 | asn: 65000 104 | ignoreMorespecifics: true 105 | 106 | options: 107 | monitorASns: 108 | 65000: 109 | group: default 110 | 2914: 111 | group: default 112 | 3333: 113 | group: default 114 | 13335: 115 | group: default -------------------------------------------------------------------------------- /src/processMonitors/uptimeHealthcheck.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Uptime from "./uptime"; 34 | import env from "../env"; 35 | 36 | export default class UptimeHealthcheck extends Uptime { 37 | 38 | constructor(connectors, params) { 39 | super(connectors, params); 40 | 41 | setInterval(this.check, params.intervalSeconds * 1000); 42 | }; 43 | 44 | check = () => { 45 | const method = ["get", "post"].includes(this.params.method) ? this.params.method : "get"; 46 | const status = this.getCurrentStatus(); 47 | 48 | if (status.warning === false) { 49 | const query = { 50 | url: this.params.url, 51 | method, 52 | responseType: "json" 53 | }; 54 | 55 | if (this.params.method === "post") { 56 | query.data = status; 57 | } 58 | 59 | this.axios(query) 60 | .catch(error => { 61 | env.logger.log({ 62 | level: "error", 63 | message: `UptimeHealthcheck cannot send heartbeat: ${error.message}` 64 | }); 65 | }); 66 | } 67 | }; 68 | 69 | 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/processMonitors/uptime.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import axios from "redaxios"; 34 | import env from "../env"; 35 | import axiosEnrich from "../utils/axiosEnrich"; 36 | 37 | export default class Uptime { 38 | 39 | constructor(connectors, params) { 40 | this.connectors = connectors; 41 | this.params = params; 42 | 43 | this.axios = axiosEnrich(axios, `${env.clientId}/${env.version}`); 44 | }; 45 | 46 | 47 | getCurrentStatus = () => { 48 | const connectors = this.connectors 49 | .getConnectors() 50 | .filter(connector => { 51 | return connector.constructor.name !== "ConnectorSwUpdates"; 52 | }) 53 | .map(connector => { 54 | return { 55 | name: connector.constructor.name, 56 | connected: connector.connected 57 | }; 58 | }); 59 | 60 | const disconnected = connectors.some(connector => !connector.connected); 61 | const rpki = env.rpki.getStatus(); 62 | 63 | const warning = disconnected || !rpki.data || rpki.stale; 64 | 65 | return { 66 | warning, 67 | connectors, 68 | rpki 69 | }; 70 | 71 | }; 72 | 73 | } 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/reports/reportSlack.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import ReportHTTP from "./reportHTTP"; 34 | 35 | export default class ReportSlack extends ReportHTTP { 36 | constructor(channels, params, env) { 37 | const templates = {}; 38 | const defaultColor = "#4287f5"; 39 | const colors = params.colors || {}; 40 | 41 | const getTemplateItem = (color) => { 42 | return JSON.stringify({ 43 | attachments: [ 44 | { 45 | color: color, 46 | title: "${channel}", 47 | type: "mrkdwn", 48 | text: "${summary}${slackUrl}" 49 | } 50 | ] 51 | }); 52 | }; 53 | 54 | for (let channel of channels) { 55 | templates[channel] = getTemplateItem(colors[channel] || defaultColor); 56 | } 57 | templates["default"] = getTemplateItem(defaultColor); 58 | 59 | const slackParams = { 60 | headers: {}, 61 | isTemplateJSON: true, 62 | showPaths: params.showPaths, 63 | hooks: params.hooks, 64 | name: "reportSlack", 65 | templates 66 | }; 67 | 68 | super(channels, slackParams, env); 69 | } 70 | } -------------------------------------------------------------------------------- /tests/config.test.yml: -------------------------------------------------------------------------------- 1 | environment: test 2 | 3 | connectors: 4 | - file: connectorTest 5 | name: tes 6 | params: 7 | testType: withdrawal 8 | 9 | monitors: 10 | - file: monitorHijack 11 | channel: hijack 12 | name: basic-hijack-detection 13 | params: 14 | thresholdMinPeers: 0 15 | 16 | - file: monitorNewPrefix 17 | channel: newprefix 18 | name: prefix-detection 19 | params: 20 | thresholdMinPeers: 0 21 | 22 | - file: monitorVisibility 23 | channel: visibility 24 | name: withdrawal-detection 25 | params: 26 | thresholdMinPeers: 4 27 | 28 | - file: monitorPath 29 | channel: path 30 | name: path-matching 31 | params: 32 | thresholdMinPeers: 0 33 | 34 | - file: monitorAS 35 | channel: misconfiguration 36 | name: asn-monitor 37 | params: 38 | skipPrefixMatch: false 39 | thresholdMinPeers: 2 40 | 41 | - file: monitorRPKI 42 | channel: rpki 43 | name: rpki-monitor 44 | params: 45 | thresholdMinPeers: 1 46 | checkUncovered: true 47 | 48 | - file: monitorROAS 49 | channel: roa 50 | name: roa-diff 51 | 52 | reports: 53 | - file: reportFile 54 | channels: 55 | - hijack 56 | - newprefix 57 | - visibility 58 | - path 59 | - misconfiguration 60 | - rpki 61 | - roa 62 | params: 63 | persistAlertData: false 64 | alertDataDirectory: alertdata/ 65 | 66 | - file: reportPullAPI 67 | channels: 68 | - hijack 69 | - newprefix 70 | - visibility 71 | - path 72 | - misconfiguration 73 | - rpki 74 | - roa 75 | params: 76 | maxAlertsAmount: 100 77 | 78 | rest: 79 | host: null 80 | port: 8011 81 | 82 | 83 | # The file containing the monitored prefixes. Please see monitored_prefixes_test.yml for an example 84 | # This is an array (use new lines and dashes!) 85 | monitoredPrefixesFiles: 86 | - prefixes.test.yml 87 | 88 | logging: 89 | directory: logs 90 | logRotatePattern: YYYY-MM-DD # Whenever the pattern changes, a new file is created and the old one rotated 91 | maxRetainedFiles: 10 92 | maxFileSizeMB: 15 93 | compressOnRotation: true 94 | 95 | checkForUpdatesAtBoot: true 96 | persistStatus: true 97 | 98 | volume: volumetests/ 99 | 100 | groupsFile: groups.test.yml 101 | 102 | processMonitors: 103 | - file: uptimeApi 104 | params: 105 | useStatusCodes: true 106 | 107 | rpki: 108 | vrpProvider: ntt 109 | refreshVrpListMinutes: 15 110 | markDataAsStaleAfterMinutes: 120 111 | 112 | notificationIntervalSeconds: 1800 # Repeat the same alert (which keeps being triggered) after x seconds 113 | alertOnlyOnce: false 114 | fadeOffSeconds: 10 115 | checkFadeOffGroupsSeconds: 2 116 | pidFile: bgpalerter.pid 117 | multiProcess: false 118 | maxMessagesPerSecond: 6000 119 | configVersion: 3 -------------------------------------------------------------------------------- /docs/path-matching.md: -------------------------------------------------------------------------------- 1 | # Path matching 2 | 3 | The component `monitorPath` allows to specify rules in order to get notified when specific conditions are matched on AS_PATHs. 4 | 5 | The rules must be expressed with regular expressions (if you need to refresh your skills or test a regular expression, I suggest [this](https://regex101.com/)). 6 | The AS_PATH parsed by BGPalerter are in the form `137,3333,1335,2914`. 7 | 8 | > Example: 9 | > The prefixes list of BGPalerter has an entry such as: 10 | > ```yaml 11 | > 165.254.255.0/24: 12 | > asn: 15562 13 | > description: an example on path matching 14 | > ignoreMorespecifics: false 15 | > path: 16 | > - match: ".*2194,1234$" 17 | > notMatch: ".*5054.*" 18 | > matchDescription: detected scrubbing center 19 | > - match: ".*123$" 20 | > notMatch: ".*5056.*" 21 | > matchDescription: other match 22 | > ``` 23 | 24 | 25 | Each item in the `path` list is a matching rule. 26 | Each matching rule is composed of: 27 | * `match`, the regular expression that will be tested on each AS path. If the expression tests positive, the BGP message triggers an alert. ASns are comma separated (see example above). 28 | * `notMatch`, the regular expression that will be tested on each AS path. If the expression tests positive, the BGP message will not trigger an alert. ASns are comma separated (see example above). 29 | * `matchDescription`, the description that will be reported in the alert in case the matchin rule results in a match. 30 | * `maxLength`, the maximum length allowed for an AS path. Longer paths will trigger an alert. 31 | * `minLength`, the minimum length allowed for an AS path. Shorter paths will trigger an alert. 32 | 33 | > Remember, the various matching rules are in OR among each other, while the fields inside a single matching rule are in AND. 34 | > This means that in the example above, the user will receive an alert if (".*2194,1234$" AND NOT ".*5054.*") OR (".*123$" AND NOT ".*5056.*") 35 | 36 | ## Some common examples 37 | 38 | * `(,|^)789$` - match paths that originate with AS789, no matter what it has in front (including nothing); 39 | * `(,|^)456,` - match any path that traverses AS456 at any point, except origin; 40 | * `(,|^)456(,|$)` - match any path that traverses AS456 at any point (including as origin, or as last AS); 41 | * `^123,456,` - match paths where the last traversed ASns were 123 and 456 (in that order); 42 | * `^123,456,789$` - match the exact path "123,457,789"; 43 | * `\[789,101112\]` - match paths containing the AS_SET [789, 101112]. 44 | 45 | 46 | ### Match regular expression with multiple conditions in AND 47 | 48 | If you want to specify multiple conditions in AND in the same match parameter, you can use the positive lookahead construct offered natively by regular expressions. 49 | A positive lookahead is in the form `(?=exp1)(?=exp2)` where `exp1` and `exp2` are regular expressions. 50 | Positive lookaheads work also for expressing negative conditions (e.g., `(?!exp)`), but in most of the cases this is redundant with the `notMatch` parameter. 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /tests/3_uptimemonitor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | const chai = require("chai"); 34 | const chaiSubset = require("chai-subset"); 35 | const axios = require("redaxios"); 36 | chai.use(chaiSubset); 37 | const expect = chai.expect; 38 | const volume = "volumetests/"; 39 | const asyncTimeout = 20000; 40 | global.EXTERNAL_VERSION_FOR_TEST = "0.0.1"; 41 | global.EXTERNAL_CONFIG_FILE = volume + "config.test.yml"; 42 | 43 | describe("Uptime Monitor", function () { 44 | 45 | const worker = require("../index"); 46 | const config = worker.config; 47 | 48 | it("uptime config", function () { 49 | expect(config.processMonitors[0]).to 50 | .containSubset({ 51 | params: { 52 | useStatusCodes: true 53 | } 54 | }); 55 | }); 56 | 57 | it("API format and header", function (done) { 58 | 59 | axios({ 60 | method: "get", 61 | responseType: "json", 62 | url: `http://localhost:8011/status` 63 | }) 64 | .then(data => { 65 | expect(data.status).to.equal(200); 66 | expect(data.data.warning).to.equal(false); 67 | done(); 68 | }) 69 | .catch(error => { 70 | console.log(error); 71 | }); 72 | 73 | }).timeout(asyncTimeout); 74 | }); -------------------------------------------------------------------------------- /tests/kafka_tests/testReportKafka.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | const chai = require("chai"); 34 | const chaiSubset = require("chai-subset"); 35 | const asyncTimeout = 60000; 36 | chai.use(chaiSubset); 37 | 38 | const {Kafka} = require("kafkajs"); 39 | const kafka = new Kafka({ 40 | clientId: "bgpalerter", 41 | brokers: ["localhost:9092"] 42 | }); 43 | 44 | global.EXTERNAL_CONFIG_FILE = "tests/kafka_tests/config.kafka.test.yml"; 45 | 46 | describe("Reports 1", function () { 47 | const worker = require("../../index"); 48 | const pubSub = worker.pubSub; 49 | 50 | it("kafka", function (done) { 51 | let doneCalled = false; 52 | const consumer = kafka.consumer({groupId: "bgpalerter"}); 53 | consumer.connect(); 54 | consumer 55 | .subscribe({topic: "bgpalerter", fromBeginning: true}) 56 | .then(() => { 57 | 58 | pubSub.publish("test-type", "visibility"); 59 | consumer.run({ 60 | eachMessage: ({topic, partition, message}) => { 61 | if (!doneCalled) { 62 | done(); 63 | doneCalled = true; 64 | } 65 | return Promise.resolve(); 66 | } 67 | }); 68 | }) 69 | .catch(error => { 70 | console.log(error); 71 | }); 72 | 73 | }).timeout(asyncTimeout); 74 | 75 | }); -------------------------------------------------------------------------------- /src/reports/reportTelegram.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import ReportHTTP from "./reportHTTP"; 34 | 35 | export default class reportTelegram extends ReportHTTP { 36 | 37 | constructor(channels, params, env) { 38 | const hooks = {}; 39 | 40 | for (let userGroup in params.chatIds) { 41 | hooks[userGroup] = params.botUrl; 42 | } 43 | hooks["default"] = params.botUrl; 44 | 45 | const telegramParams = { 46 | headers: {}, 47 | isTemplateJSON: true, 48 | showPaths: params.showPaths, 49 | hooks: hooks, 50 | name: "reportTelegram", 51 | templates: {} 52 | }; 53 | 54 | super(channels, telegramParams, env); 55 | this.chatIds = params.chatIds; 56 | 57 | if (!params.botUrl) { 58 | this.logger.log({ 59 | level: "error", 60 | message: `${this.name} is not enabled: no botUrl provided` 61 | }); 62 | this.enabled = false; 63 | } 64 | 65 | if (!params.chatIds || !params.chatIds["default"]) { 66 | this.logger.log({ 67 | level: "error", 68 | message: `${this.name} is not enabled: no default chat id provided` 69 | }); 70 | this.enabled = false; 71 | } 72 | }; 73 | 74 | getTemplate = (group, channel, content) => { 75 | return JSON.stringify({ 76 | "chat_id": this.chatIds[group] || this.chatIds["default"], 77 | "text": "${summary}${markDownUrl}", 78 | "parse_mode": "markdown", 79 | "disable_web_page_preview": true 80 | }); 81 | }; 82 | } -------------------------------------------------------------------------------- /tests/4_groups.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | const chai = require("chai"); 34 | const fs = require("fs"); 35 | const chaiSubset = require("chai-subset"); 36 | chai.use(chaiSubset); 37 | const expect = chai.expect; 38 | const volume = "volumetests/"; 39 | const asyncTimeout = 20000; 40 | 41 | // Prepare test environment 42 | if (!fs.existsSync(volume)) { 43 | fs.mkdirSync(volume, {recursive: true}); 44 | } 45 | fs.copyFileSync("tests/config.test.yml", volume + "config.test.yml"); 46 | fs.copyFileSync("tests/prefixes.test.yml", volume + "prefixes.test.yml"); 47 | fs.copyFileSync("tests/groups.test.yml", volume + "groups.test.yml"); 48 | 49 | global.EXTERNAL_CONFIG_FILE = volume + "config.test.yml"; 50 | 51 | const worker = require("../index"); 52 | 53 | describe("External groups file", function () { 54 | 55 | it("load groups", function () { 56 | const config = worker.config; 57 | expect(config.groupsFile).to.equal("groups.test.yml"); 58 | expect(config.reports[0].params.userGroups).to 59 | .containSubset({ 60 | test: [ 61 | "filename" 62 | ] 63 | }); 64 | }) 65 | .timeout(asyncTimeout); 66 | 67 | it("watch groups", function (done) { 68 | 69 | fs.copyFileSync("tests/groups.test.after.yml", "volumetests/groups.test.yml"); 70 | 71 | setTimeout(() => { 72 | const config = worker.config; 73 | expect(config.reports[0].params.userGroups).to 74 | .containSubset({ 75 | test: [ 76 | "filename-after" 77 | ] 78 | }); 79 | done(); 80 | }, 10000); 81 | 82 | }) 83 | .timeout(asyncTimeout); 84 | }); -------------------------------------------------------------------------------- /tests/reports_tests/testReportSyslog.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | const chai = require("chai"); 34 | const chaiSubset = require("chai-subset"); 35 | const Syslogd = require("syslogd"); 36 | const expect = chai.expect; 37 | const asyncTimeout = 20000; 38 | chai.use(chaiSubset); 39 | 40 | global.EXTERNAL_VERSION_FOR_TEST = "0.0.1"; 41 | global.EXTERNAL_CONFIG_FILE = "tests/reports_tests/config.reports.test.yml"; 42 | 43 | describe("Reports 1", function () { 44 | const worker = require("../../index"); 45 | const pubSub = worker.pubSub; 46 | 47 | it("syslog", function (done) { 48 | let doneCalled = false; 49 | 50 | let expectedData = [ 51 | "The prefix 2a00:5884::/32 (alarig fix test) has been withdrawn.", 52 | "The prefix 165.254.225.0/24 (description 1) has been withdrawn.", 53 | "The prefix 2001:db8:123::/48 (exact matching test) has been withdrawn." 54 | ]; 55 | 56 | Syslogd(function (info) { 57 | if (!doneCalled) { 58 | expect(info.hostname).to.equals("127.0.0.1"); 59 | const object = expectedData.filter(i => info.msg.startsWith(i))[0]; 60 | expectedData = expectedData.filter(i => i !== object); 61 | 62 | if (object) { 63 | if (expectedData.length === 0) { 64 | done(); 65 | doneCalled = true; 66 | } 67 | } 68 | } 69 | }) 70 | .listen(1516, function (error) { 71 | if (error) { 72 | console.log(error); 73 | } 74 | }); 75 | 76 | pubSub.publish("test-type", "visibility"); 77 | }).timeout(asyncTimeout); 78 | 79 | }); -------------------------------------------------------------------------------- /src/connectors/connectorSwUpdates.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Connector from "./connector"; 34 | import semver from "semver"; 35 | 36 | export default class ConnectorSwUpdates extends Connector { 37 | 38 | constructor(name, params, env) { 39 | super(name, params, env); 40 | } 41 | 42 | static transform = (message) => { 43 | return [message]; 44 | }; 45 | 46 | connect = () => 47 | new Promise((resolve, reject) => { 48 | resolve(true); 49 | }); 50 | 51 | _checkForUpdates = () => { 52 | return this.axios({ 53 | responseType: "json", 54 | url: "https://raw.githubusercontent.com/nttgin/BGPalerter/main/package.json" 55 | }) 56 | .then(data => { 57 | 58 | if (data && data.data && data.data.version && semver.gt(data.data.version, this.version)) { 59 | this._message({ 60 | type: "software-update", 61 | currentVersion: this.version, 62 | newVersion: data.data.version, 63 | repo: "https://github.com/nttgin/BGPalerter/releases" 64 | }); 65 | } 66 | }) 67 | .catch(() => { 68 | this.logger.log({ 69 | level: "error", 70 | message: "It was not possible to check for software updates" 71 | }); 72 | }); 73 | }; 74 | 75 | subscribe = (input) => 76 | new Promise((resolve, reject) => { 77 | if (this.config.checkForUpdatesAtBoot) { 78 | setTimeout(this._checkForUpdates, 20000); // Check after 20 seconds from boot 79 | } 80 | setInterval(this._checkForUpdates, 1000 * 3600 * 24 * 5); // Check every 5 days 81 | resolve(true); 82 | }); 83 | }; 84 | -------------------------------------------------------------------------------- /tests/reports_tests/config.reports.test.yml: -------------------------------------------------------------------------------- 1 | environment: test 2 | 3 | connectors: 4 | - file: connectorTest 5 | name: tes 6 | params: 7 | testType: withdrawal 8 | 9 | monitors: 10 | - file: monitorHijack 11 | channel: hijack 12 | name: basic-hijack-detection 13 | params: 14 | thresholdMinPeers: 0 15 | 16 | - file: monitorNewPrefix 17 | channel: newprefix 18 | name: prefix-detection 19 | params: 20 | thresholdMinPeers: 0 21 | 22 | - file: monitorVisibility 23 | channel: visibility 24 | name: withdrawal-detection 25 | params: 26 | thresholdMinPeers: 4 27 | 28 | - file: monitorPath 29 | channel: path 30 | name: path-matching 31 | params: 32 | thresholdMinPeers: 0 33 | 34 | - file: monitorAS 35 | channel: misconfiguration 36 | name: asn-monitor 37 | params: 38 | thresholdMinPeers: 2 39 | 40 | # - file: monitorRPKI 41 | # channel: rpki 42 | # name: rpki-monitor 43 | # params: 44 | # thresholdMinPeers: 1 45 | # checkUncovered: true 46 | 47 | 48 | reports: 49 | - file: reportSyslog 50 | channels: 51 | - hijack 52 | - newprefix 53 | - visibility 54 | - path 55 | - asn-monitor 56 | - misconfiguration 57 | - rpki 58 | params: 59 | host: 127.0.0.1 60 | port: 1516 61 | transport: udp 62 | templates: # See here how to write a template https://github.com/nttgin/BGPalerter/blob/main/docs/context.md 63 | default: "++BGPalerter-3-${type}: ${summary}|${earliest}|${latest}" 64 | hijack: "++BGPalerter-5-${type}: ${summary}|${prefix}|${description}|${asn}|${newprefix}|${neworigin}|${earliest}|${latest}|${peers}" 65 | newprefix: "++BGPalerter-4-${type}: ${summary}|${prefix}|${description}|${asn}|${newprefix}|${neworigin}|${earliest}|${latest}|${peers}" 66 | visibility: "++BGPalerter-5-${type}: ${summary}|${prefix}|${description}|${asn}|${earliest}|${latest}|${peers}" 67 | misconfiguration: "++BGPalerter-3-${type}: ${summary}|${asn}|${prefix}|${earliest}|${latest}" 68 | 69 | - file: reportHTTP 70 | channels: 71 | - hijack 72 | - newprefix 73 | - visibility 74 | - path 75 | - misconfiguration 76 | - rpki 77 | params: 78 | templates: # See here how to write a template https://github.com/nttgin/BGPalerter/blob/main/docs/context.md 79 | default: '{"text": "${summary}"}' 80 | headers: 81 | isTemplateJSON: true 82 | showPaths: 5 # Amount of AS_PATHs to report in the alert 83 | hooks: 84 | default: http://localhost:8090/test 85 | 86 | # The file containing the monitored prefixes. Please see monitored_prefixes_test.yml for an example 87 | # This is an array (use new lines and dashes!) 88 | monitoredPrefixesFiles: 89 | - tests/prefixes.test.yml 90 | 91 | logging: 92 | directory: logs 93 | logRotatePattern: YYYY-MM-DD # Whenever the pattern changes, a new file is created and the old one rotated 94 | maxRetainedFiles: 10 95 | maxFileSizeMB: 15 96 | compressOnRotation: true 97 | 98 | checkForUpdatesAtBoot: false 99 | persistStatus: false 100 | 101 | 102 | notificationIntervalSeconds: 1800 # Repeat the same alert (which keeps being triggered) after x seconds 103 | alertOnlyOnce: false 104 | fadeOffSeconds: 10 105 | checkFadeOffGroupsSeconds: 2 106 | pidFile: bgpalerter.pid 107 | multiProcess: false 108 | maxMessagesPerSecond: 6000 109 | configVersion: 3 -------------------------------------------------------------------------------- /src/reports/reportWebex.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Report from "./report"; 34 | 35 | export default class ReportWebex extends Report { 36 | 37 | constructor(channels, params, env) { 38 | super(channels, params, env); 39 | 40 | 41 | if (!this.getUserGroup("default")) { 42 | this.logger.log({ 43 | level: "error", 44 | message: `Webex is not enabled: no default group defined` 45 | }); 46 | this.enabled = false; 47 | } 48 | }; 49 | 50 | getUserGroup = (group) => { 51 | const groups = this.params.hooks || this.params.userGroups; 52 | 53 | return groups[group] || groups["default"]; 54 | }; 55 | 56 | _sendWebexMessage = (url, message, content) => { 57 | this.logger.log({ 58 | level: "info", 59 | message: `[reportWebex] sending report to: ${url}` 60 | }); 61 | 62 | this.axios({ 63 | url, 64 | method: "POST", 65 | resposnseType: "json", 66 | data: { 67 | markdown: `**${message}**: ${content.message}` 68 | } 69 | }) 70 | .catch((error) => { 71 | this.logger.log({ 72 | level: "error", 73 | message: error 74 | }); 75 | }); 76 | }; 77 | 78 | report = (message, content) => { 79 | if (this.enabled) { 80 | let groups = content.data.map(i => i.matchedRule.group).filter(i => i != null); 81 | 82 | groups = (groups.length) ? [...new Set(groups)] : ["default"]; 83 | 84 | for (let group of groups) { 85 | const url = this.getUserGroup(group); 86 | if (url) { 87 | this._sendWebexMessage(url, message, content); 88 | } 89 | } 90 | } 91 | 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /tests/reports_tests/testsReportHttp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | const chai = require("chai"); 34 | const chaiSubset = require("chai-subset"); 35 | const express = require("express"); 36 | const bodyParser = require("body-parser"); 37 | const asyncTimeout = 120000; 38 | chai.use(chaiSubset); 39 | const assert = chai.assert; 40 | 41 | global.EXTERNAL_VERSION_FOR_TEST = "0.0.1"; 42 | global.EXTERNAL_CONFIG_FILE = "tests/reports_tests/config.reports.test.yml"; 43 | 44 | describe("Reports 2", function () { 45 | const worker = require("../../index"); 46 | const pubSub = worker.pubSub; 47 | 48 | it("reportHTTP", function (done) { 49 | const server = express(); 50 | server.use(bodyParser.json()); 51 | let expectedData = [ 52 | "The prefix 2a00:5884::/32 (alarig fix test) is announced by AS15563 instead of AS204092, and AS45. Top 1 most used AS paths: [2,3,15563].", 53 | "A new prefix 165.254.255.0/25 is announced by AS15562, and AS4. It should be instead 165.254.255.0/24 (description 2) announced by AS15562. Top 1 most used AS paths: [2,3,[15562,4]].", 54 | "A new prefix 2a00:5884:ffff::/48 is announced by AS208585. It should be instead 2a00:5884::/32 (alarig fix test) announced by AS204092, and AS45. Top 1 most used AS paths: [2,3,208585]." 55 | ]; 56 | 57 | pubSub.publish("test-type", "hijack"); 58 | server.post("/test", function (req, res) { 59 | const text = req.body.text; 60 | if (expectedData.includes(text)) { 61 | expectedData = expectedData.filter(i => i !== text); 62 | } else { 63 | assert.fail(text, "none", "The message is not expected"); 64 | } 65 | 66 | if (expectedData.length === 0) { 67 | done(); 68 | } 69 | res.status(200).end(); 70 | }); 71 | server.listen(8090); 72 | 73 | }).timeout(asyncTimeout); 74 | }); -------------------------------------------------------------------------------- /src/monitors/monitorNewPrefix.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Monitor from "./monitor"; 34 | import ipUtils from "ip-sub"; 35 | 36 | export default class MonitorNewPrefix extends Monitor { 37 | 38 | constructor(name, channel, params, env, input) { 39 | super(name, channel, params, env, input); 40 | this.thresholdMinPeers = params?.thresholdMinPeers ?? 3; 41 | this.updateMonitoredResources(); 42 | }; 43 | 44 | updateMonitoredResources = () => { 45 | this.monitored = this.input.getMonitoredMoreSpecifics(); 46 | }; 47 | 48 | filter = (message) => { 49 | return message.type === "announcement"; 50 | }; 51 | 52 | squashAlerts = (alerts) => { 53 | const peers = this.getPeers(alerts); 54 | 55 | if (peers >= this.thresholdMinPeers) { 56 | const message = alerts[0].matchedMessage; 57 | const matchedRule = alerts[0].matchedRule; 58 | 59 | return `A new prefix ${message.prefix} is announced by ${message.originAS}. It is a more specific of ${matchedRule.prefix} (${matchedRule.description}). Maybe you need to update your BGPalerter prefix list.`; 60 | } 61 | 62 | return false; 63 | }; 64 | 65 | monitor = (message) => { 66 | 67 | const messagePrefix = message.prefix; 68 | const matchedRules = this.getMoreSpecificMatches(messagePrefix, false); 69 | 70 | for (let matchedRule of matchedRules) { 71 | if (!matchedRule.ignore && 72 | matchedRule.asn.includes(message.originAS) && 73 | !ipUtils._isEqualPrefix(matchedRule.prefix, messagePrefix)) { 74 | 75 | this.publishAlert(message.originAS.getId() + "-" + message.prefix, 76 | matchedRule.asn.getId(), 77 | matchedRule, 78 | message, 79 | {}); 80 | } 81 | } 82 | 83 | return Promise.resolve(true); 84 | }; 85 | 86 | } -------------------------------------------------------------------------------- /src/config/configYml.js: -------------------------------------------------------------------------------- 1 | import Config from "./config"; 2 | import yaml from "js-yaml"; 3 | import fs from "fs"; 4 | import path from "path"; 5 | 6 | export default class ConfigYml extends Config { 7 | constructor(params) { 8 | super(params); 9 | this.configFile = global.EXTERNAL_CONFIG_FILE || 10 | ((global.EXTERNAL_VOLUME_DIRECTORY) 11 | ? global.EXTERNAL_VOLUME_DIRECTORY + "config.yml" 12 | : path.resolve(process.cwd(), "config.yml")); 13 | 14 | this.groupsFile = global.EXTERNAL_GROUP_FILE; 15 | 16 | console.log("Loaded config:", this.configFile); 17 | }; 18 | 19 | save = (config) => { 20 | try { 21 | fs.writeFileSync(this.configFile, yaml.dump(config)); 22 | yaml.load(fs.readFileSync(this.configFile, "utf8")); // Test readability and format 23 | } catch (error) { 24 | throw new Error("Cannot save the configuration in " + this.configFile); 25 | } 26 | }; 27 | 28 | retrieve = () => { 29 | const ymlBasicConfig = yaml.dump(this.default); 30 | 31 | if (fs.existsSync(this.configFile)) { 32 | try { 33 | const config = yaml.load(fs.readFileSync(this.configFile, "utf8")) || this.default; 34 | this._readUserGroupsFiles(config); 35 | 36 | return config; 37 | } catch (error) { 38 | throw new Error("The file " + this.configFile + " is not valid yml: " + error.message.split(":")[0]); 39 | } 40 | } else { 41 | console.log("Impossible to load config.yml. A default configuration file has been generated."); 42 | 43 | this.downloadDefault() 44 | .then(data => { 45 | fs.writeFileSync(this.configFile, data); 46 | yaml.load(fs.readFileSync(this.configFile, "utf8")); // Test readability and format 47 | 48 | this._readUserGroupsFiles(data); 49 | }) 50 | .catch(() => { 51 | fs.writeFileSync(this.configFile, ymlBasicConfig); // Download failed, write simple default config 52 | }); 53 | 54 | return this.default; 55 | } 56 | }; 57 | 58 | _readUserGroupsFiles = (config) => { 59 | if (config.groupsFile) { 60 | this.groupsFile = ((config.volume) 61 | ? config.volume + config.groupsFile 62 | : path.resolve(process.cwd(), config.groupsFile)); 63 | 64 | const userGroups = yaml.load(fs.readFileSync(this.groupsFile, "utf8")); 65 | 66 | for (let report of config.reports) { 67 | const name = report.file; 68 | const groups = userGroups[name]; 69 | if (userGroups[name]) { 70 | report.params.userGroups = groups; 71 | } 72 | } 73 | 74 | fs.watchFile(this.groupsFile, () => { 75 | if (this._watchPrefixFileTimer) { 76 | clearTimeout(this._watchPrefixFileTimer); 77 | } 78 | this._watchPrefixFileTimer = setTimeout(() => { 79 | const userGroups = yaml.load(fs.readFileSync(this.groupsFile, "utf8")); 80 | 81 | for (let report of config.reports) { 82 | const name = report.file; 83 | const groups = userGroups[name]; 84 | 85 | if (userGroups[name]) { 86 | report.params.userGroups = groups; 87 | } 88 | } 89 | }, 5000); 90 | }); 91 | } 92 | 93 | }; 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/reports/reportFile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Report from "./report"; 34 | import fs from "fs"; 35 | 36 | export default class ReportFile extends Report { 37 | 38 | constructor(channels, params, env) { 39 | super(channels, params, env); 40 | 41 | this.persistAlerts = params.persistAlertData; 42 | this.alertsDirectory = env.config.volume + params.alertDataDirectory; 43 | if (this.persistAlerts && !this.alertsDirectory) { 44 | this.persistAlerts = false; 45 | this.logger.log({ 46 | level: "error", 47 | message: "Cannot persist alert data, the parameter alertDataDirectory is missing." 48 | }); 49 | } 50 | this.latestTimestamps = []; 51 | this.timestampsBacklogSize = 100; 52 | } 53 | 54 | writeDataOnFile = (message) => { 55 | try { 56 | const timestamp = `${message.earliest}-${message.latest}`; 57 | this.latestTimestamps.push(timestamp); 58 | const count = this.latestTimestamps.filter(i => i === timestamp).length; 59 | this.latestTimestamps = this.latestTimestamps.slice(-this.timestampsBacklogSize); 60 | const filename = `${this.alertsDirectory}/alert-${timestamp}-${count}.json`; 61 | 62 | if (!fs.existsSync(this.alertsDirectory)) { 63 | fs.mkdirSync(this.alertsDirectory, {recursive: true}); 64 | } 65 | 66 | fs.writeFileSync(filename, JSON.stringify(message)); 67 | } catch (error) { 68 | this.logger.log({ 69 | level: "error", 70 | message: error 71 | }); 72 | } 73 | }; 74 | 75 | report = (message, content) => { 76 | this.logger.log({ 77 | level: "verbose", 78 | message: content.message 79 | }); 80 | 81 | if (this.persistAlerts) { 82 | this.writeDataOnFile(content); 83 | } 84 | }; 85 | } -------------------------------------------------------------------------------- /tests/rpki_tests/tests.external-missing-roas.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | const chai = require("chai"); 34 | const chaiSubset = require("chai-subset"); 35 | const fs = require("fs"); 36 | const expect = chai.expect; 37 | const asyncTimeout = 120000; 38 | chai.use(chaiSubset); 39 | 40 | global.EXTERNAL_CONFIG_FILE = "tests/rpki_tests/config.rpki.test.external.yml"; 41 | fs.copyFileSync("tests/rpki_tests/vrp.missing.json", "tests/rpki_tests/vrp.json"); 42 | 43 | const worker = require("../../index"); 44 | const pubSub = worker.pubSub; 45 | 46 | 47 | pubSub.publish("test-type", "rpki"); 48 | 49 | describe("RPKI monitoring external", function () { 50 | 51 | it("missing roas", function (done) { 52 | 53 | const expectedData = { 54 | 55 | "a82_112_100_0_24-2914-null": { 56 | id: "a82_112_100_0_24-2914-null", 57 | origin: "roa-monitor", 58 | affected: "82.112.100.0/24", 59 | message: "The route 82.112.100.0/24 announced by AS2914 is no longer covered by a ROA" 60 | } 61 | 62 | }; 63 | 64 | let rpkiTestCompletedExternal = false; 65 | 66 | pubSub.subscribe("roa", function (message, type) { 67 | try { 68 | 69 | message = JSON.parse(JSON.stringify(message)); 70 | const id = message.id; 71 | 72 | if (!rpkiTestCompletedExternal && Object.keys(expectedData).includes(id)) { 73 | expect(message).to.containSubset(expectedData[id]); 74 | 75 | expect(message).to.contain 76 | .keys([ 77 | "latest", 78 | "earliest" 79 | ]); 80 | 81 | rpkiTestCompletedExternal = true; 82 | done(); 83 | } 84 | } catch (error) { 85 | rpkiTestCompletedExternal = true; 86 | done(error); 87 | } 88 | }); 89 | 90 | }).timeout(asyncTimeout); 91 | }); -------------------------------------------------------------------------------- /src/monitors/monitorVisibility.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Monitor from "./monitor"; 34 | import ipUtils from "ip-sub"; 35 | 36 | export default class MonitorVisibility extends Monitor { 37 | 38 | constructor(name, channel, params, env, input) { 39 | super(name, channel, params, env, input); 40 | this.thresholdMinPeers = params?.thresholdMinPeers ?? 40; 41 | if (params.threshold) { 42 | throw new Error("The parameter threshold has been replaced by thresholdMinPeers and it will be soon deprecated.") 43 | } 44 | this.updateMonitoredResources(); 45 | }; 46 | 47 | updateMonitoredResources = () => { 48 | this.monitored = this.input.getMonitoredPrefixes(); 49 | }; 50 | 51 | filter = (message) => { 52 | return message.type === "withdrawal"; 53 | }; 54 | 55 | squashAlerts = (alerts) => { 56 | const peers = this.getPeers(alerts); 57 | 58 | if (peers >= this.thresholdMinPeers) { 59 | return (peers === 1) ? 60 | `The prefix ${alerts[0].matchedMessage.prefix} (${alerts[0].matchedRule.description}) it's no longer visible (withdrawn) from the peer ${alerts[0].matchedMessage.peer}` : 61 | `The prefix ${alerts[0].matchedMessage.prefix} (${alerts[0].matchedRule.description}) has been withdrawn. It is no longer visible from ${peers} peers`; 62 | } else { 63 | return false; 64 | } 65 | }; 66 | 67 | monitor = (message) => { 68 | const messagePrefix = message.prefix; 69 | const matchedRules = this.getMoreSpecificMatches(messagePrefix, false); 70 | 71 | for (let matchedRule of matchedRules) { 72 | if (!matchedRule.ignore && ipUtils._isEqualPrefix(matchedRule.prefix, messagePrefix)) { 73 | 74 | let key = matchedRule.prefix; 75 | 76 | this.publishAlert(key, 77 | matchedRule.asn.getId(), 78 | matchedRule, 79 | message, 80 | {}); 81 | } 82 | } 83 | 84 | return Promise.resolve(true); 85 | }; 86 | } -------------------------------------------------------------------------------- /src/reports/reportMatrix.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import ReportHTTP from "./reportHTTP"; 34 | import {v4 as uuidv4} from "uuid"; 35 | import brembo from "brembo"; 36 | 37 | export default class reportMatrix extends ReportHTTP { 38 | constructor(channels, params, env) { 39 | const hooks = {}; 40 | 41 | for (let userGroup in params?.roomIds ?? []) { 42 | hooks[userGroup] = brembo.build(params?.homeserverUrl, { 43 | path: ["_matrix", "client", "v3", "rooms", encodeURIComponent(params?.roomIds[userGroup]), "send", "m.room.message"] 44 | }); 45 | } 46 | 47 | const matrixParams = { 48 | headers: { 49 | "Content-Type": "application/json", 50 | "Authorization": "Bearer " + params.accessToken 51 | }, 52 | isTemplateJSON: true, 53 | showPaths: params.showPaths, 54 | hooks: hooks, 55 | name: "reportMatrix", 56 | method: "put", 57 | templates: {} 58 | }; 59 | 60 | super(channels, matrixParams, env); 61 | this.roomIds = params.roomIds; 62 | 63 | if (!params.homeserverUrl || !params.accessToken) { 64 | this.logger.log({ 65 | level: "error", 66 | message: `${this.name} reporting is not enabled: homeserverUrl and accessToken are required` 67 | }); 68 | this.enabled = false; 69 | } 70 | 71 | if (!params.roomIds || !params.roomIds["default"]) { 72 | this.logger.log({ 73 | level: "error", 74 | message: `${this.name} reporting is not enabled: no default room id provided` 75 | }); 76 | this.enabled = false; 77 | } 78 | }; 79 | 80 | getUserGroup = (group) => { 81 | 82 | const transactionId = uuidv4(); 83 | const groups = this.params.hooks || this.params.userGroups || {}; 84 | const baseUrl = groups[group] || groups["default"]; 85 | 86 | return brembo.build(baseUrl, {path: [transactionId]}); 87 | }; 88 | 89 | getTemplate = (group, channel, content) => { 90 | return JSON.stringify({ 91 | "msgtype": "m.text", 92 | "body": "${summary}${markDownUrl}" 93 | }); 94 | }; 95 | } 96 | 97 | -------------------------------------------------------------------------------- /src/monitors/monitorHijack.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Monitor from "./monitor"; 34 | import ipUtils from "ip-sub"; 35 | 36 | export default class MonitorHijack extends Monitor { 37 | 38 | constructor(name, channel, params, env, input) { 39 | super(name, channel, params, env, input); 40 | this.thresholdMinPeers = params?.thresholdMinPeers ?? 2; 41 | this.updateMonitoredResources(); 42 | }; 43 | 44 | updateMonitoredResources = () => { 45 | this.monitored = this.input.getMonitoredPrefixes(); 46 | }; 47 | 48 | filter = (message) => { 49 | return message.type === "announcement"; 50 | }; 51 | 52 | squashAlerts = (alerts) => { 53 | const peers = this.getPeers(alerts); 54 | 55 | if (peers >= this.thresholdMinPeers) { 56 | const matchedRule = alerts[0].matchedRule; 57 | const message = alerts[0].matchedMessage; 58 | const asnText = matchedRule.asn; 59 | 60 | return (ipUtils._isEqualPrefix(message.prefix, matchedRule.prefix)) ? 61 | `The prefix ${matchedRule.prefix} (${matchedRule.description}) is announced by ${message.originAS} instead of ${asnText}` : 62 | `A new prefix ${message.prefix} is announced by ${message.originAS}. ` + 63 | `It should be instead ${matchedRule.prefix} (${matchedRule.description}) announced by ${asnText}`; 64 | } 65 | 66 | return false; 67 | }; 68 | 69 | validate = (message, matchedRule) => { 70 | this.rpki.addToValidationQueue(message, matchedRule, this._validate); 71 | }; 72 | 73 | _validate = (result, message, matchedRule) => { 74 | if (!result.valid) { 75 | this.publishAlert(message.originAS.getId() + "-" + message.prefix, 76 | matchedRule.asn.getId(), 77 | matchedRule, 78 | message, 79 | {}); 80 | } 81 | }; 82 | 83 | monitor = (message) => { 84 | const messagePrefix = message.prefix; 85 | const matchedRules = this.getMoreSpecificMatches(messagePrefix, false); 86 | 87 | for (let matchedRule of matchedRules) { 88 | if (!matchedRule.ignore && !matchedRule.asn.includes(message.originAS)) { 89 | this.validate(message, matchedRule); 90 | } 91 | } 92 | 93 | return Promise.resolve(true); 94 | }; 95 | 96 | } -------------------------------------------------------------------------------- /docs/process-monitors.md: -------------------------------------------------------------------------------- 1 | # Process monitoring 2 | 3 | Since version 1.22.0 it is possible to monitor the status of the BGPalerter process. 4 | 5 | There are various approaches for monitoring the status of BGPalerter, each implemented in a specific module. 6 | You can declare the modules you want to load/enable in `config.yml`, as follows: 7 | 8 | ```yaml 9 | processMonitors: 10 | - file: uptimeApi 11 | params: 12 | useStatusCodes: true 13 | - file: uptimeHealthcheck 14 | params: 15 | url: url_to_poll 16 | intervalSeconds: 300 17 | method: get 18 | 19 | - file: sentryModule 20 | params: 21 | dsn: https://@sentry.io/ 22 | ``` 23 | 24 | 25 | ## uptimeApi 26 | 27 | The uptimeApi module enables an API to retrieve the current status of BGPalerter. 28 | 29 | By default the API is reachable at `http://localhost:8011/status` and provides a summary of the status of various components of BGPalerter. If any of the components is having a problem, the attribute `warning` is set to true. 30 | 31 | The following is an example of the API output. 32 | 33 | ``` 34 | { 35 | "warning": false, 36 | "connectors": [ 37 | { 38 | "name": "ConnectorRIS", 39 | "connected": true 40 | } 41 | ] 42 | } 43 | ``` 44 | 45 | In `config.yml` the uptimeApi is declared as: 46 | 47 | ```yaml 48 | processMonitors: 49 | - file: uptimeApi 50 | params: 51 | useStatusCodes: true 52 | ``` 53 | 54 | When the uptimeApi block is commented/deleted from the config file, no extra dependencies are loaded and no open port is required. 55 | 56 | 57 | The REST API uses the generic `rest` configuration in `config.yml`. Read [here](configuration.md) or see `config.yml.example` for more information. 58 | The REST configuration is by default: 59 | ```yaml 60 | rest: 61 | host: localhost 62 | port: 8011 63 | ``` 64 | 65 | 66 | 67 | The API, in addition to the JSON answer, can use HTTP status codes for an easier integration with Nagios and similar. 68 | 69 | Parameters for this module are: 70 | 71 | |Parameter| Description| 72 | |---|---| 73 | |useStatusCodes| A boolean that if set to true enables HTTP status codes in the response. Nothing changes in the JSON output provided by the API. | 74 | 75 | 76 | ## uptimeHealthcheck 77 | 78 | The uptimeHealthcheck module is a component that will start polling a provided URL at a regular interval. 79 | 80 | This can be used to send a heartbeat signal to a monitoring system (e.g., https://healthchecks.io/). 81 | If there is any warning about any component activated in BGPalerter, the heartbeat will not be issued (independently from the fact that the process is still running). 82 | 83 | 84 | In `config.yml` the uptimeHealthcheck is declared as: 85 | 86 | ```yaml 87 | processMonitors: 88 | 89 | - file: uptimeHealthcheck 90 | params: 91 | url: url_to_poll 92 | intervalSeconds: 300 93 | method: get 94 | ``` 95 | 96 | If the `method` parameter is set to `post`, the body of the request will contain a detailed status of BGPalerter. 97 | The status is reported in the same JSON format described for the uptimeApi module. 98 | 99 | Parameters for this module are: 100 | 101 | |Parameter| Description| 102 | |---|---| 103 | |url| The URL to be polled periodically. | 104 | |intervalSecond| The interval (in seconds) between HTTP requests. | 105 | |method| The method used for the HTTP request. It can be `get` or `post`. | 106 | 107 | 108 | ## sentryModule 109 | 110 | The sentryModule is a component that allows the monitoring of the BGPalerter process for runtime exceptions. 111 | Useful especially for testing new experimental modules. 112 | 113 | To enable this feature, create a new project on your Sentry server and grab the generated DSN. 114 | 115 | In `config.yml` the sentryModule is declared as: 116 | 117 | ```yaml 118 | processMonitors: 119 | 120 | - file: sentryModule 121 | params: 122 | dsn: https://@sentry.io/ 123 | ``` 124 | 125 | Parameters for this module are: 126 | 127 | |Parameter| Description| 128 | |---|---| 129 | |dsn| The DSN where the logs will be sent. | 130 | -------------------------------------------------------------------------------- /src/consumer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | export default class Consumer { 34 | 35 | constructor(env, input) { 36 | this.logger = env.logger; 37 | this.connectors = {}; 38 | 39 | for (let connector of env.config.connectors) { 40 | this.connectors[connector.name] = connector.class; 41 | } 42 | 43 | try { 44 | 45 | this.monitors = env.config.monitors 46 | .map(monitor => new monitor.class(monitor.name, monitor.channel, monitor.params || {}, env, input)); 47 | 48 | this.reports = env.config.reports 49 | .map(report => new report.class(report.channels, report.params || {}, env)); 50 | 51 | } catch (error) { 52 | this.logger.log({ 53 | level: "error", 54 | message: error 55 | }); 56 | } 57 | process.on("message", this.dispatch); 58 | env.pubSub.subscribe("data", this.dispatch); 59 | }; 60 | 61 | dispatch = (buffer) => { 62 | try { 63 | for (let data of buffer) { 64 | 65 | const connector = data.connector; 66 | const messagesRaw = data.message; 67 | const messages = this.connectors[connector].transform(messagesRaw) || []; 68 | 69 | for (let monitor of this.monitors) { 70 | 71 | try { 72 | // Blocking filtering to reduce stack usage 73 | for (const message of messages.filter(monitor.filter)) { 74 | 75 | // Promise call to reduce waiting times 76 | monitor 77 | .monitor(message) 78 | .catch(error => { 79 | this.logger.log({ 80 | level: "error", 81 | message: error 82 | }); 83 | }); 84 | } 85 | } catch (error) { 86 | this.logger.log({ 87 | level: "error", 88 | message: error.message 89 | }); 90 | } 91 | } 92 | } 93 | } catch (error) { 94 | this.logger.log({ 95 | level: "error", 96 | message: error.message 97 | }); 98 | } 99 | }; 100 | 101 | } 102 | 103 | 104 | -------------------------------------------------------------------------------- /tests/dump_tests/tests.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | const chai = require("chai"); 34 | const fs = require("fs"); 35 | const chaiSubset = require("chai-subset"); 36 | chai.use(chaiSubset); 37 | const expect = chai.expect; 38 | const volume = "volumetests/"; 39 | const asyncTimeout = 120000; 40 | global.EXTERNAL_VERSION_FOR_TEST = "0.0.1"; 41 | global.EXTERNAL_CONFIG_FILE = volume + "config.test.yml"; 42 | 43 | // Prepare test environment 44 | if (!fs.existsSync(volume)) { 45 | fs.mkdirSync(volume, {recursive: true}); 46 | } else { 47 | fs.rmSync(volume, {recursive: true}); 48 | fs.mkdirSync(volume, {recursive: true}); 49 | } 50 | fs.copyFileSync("tests/dump_tests/config.test.yml", volume + "config.test.yml"); 51 | fs.copyFileSync("tests/dump_tests/prefixes.test.yml", volume + "prefixes.test.yml"); 52 | 53 | describe("Alerting", function () { 54 | 55 | it("RIS dump test", function (done) { 56 | 57 | const worker = require("../../index"); 58 | const pubSub = worker.pubSub; 59 | 60 | const expectedData = { 61 | "3333-193.0.20.0/23": { 62 | id: "3333-193.0.20.0/23", 63 | truncated: false, 64 | origin: "basic-hijack-detection", 65 | affected: 1234, 66 | message: "The prefix 193.0.20.0/23 (No description provided) is announced by AS3333 instead of AS1234", 67 | data: [ 68 | { 69 | affected: 1234 70 | } 71 | ] 72 | } 73 | }; 74 | 75 | let dumpTestCompleted = false; 76 | pubSub.subscribe("hijack", function (message, type) { 77 | try { 78 | if (!dumpTestCompleted) { 79 | message = JSON.parse(JSON.stringify(message)); 80 | const id = message.id; 81 | 82 | expect(Object.keys(expectedData).includes(id)).to.equal(true); 83 | expect(expectedData[id] != null).to.equal(true); 84 | 85 | expect(message).to 86 | .containSubset(expectedData[id]); 87 | 88 | delete expectedData[id]; 89 | if (Object.keys(expectedData).length === 0) { 90 | setTimeout(() => { 91 | dumpTestCompleted = true; 92 | done(); 93 | }, 5000); 94 | } 95 | } 96 | } catch (error) { 97 | dumpTestCompleted = true; 98 | done(error); 99 | } 100 | }); 101 | 102 | }).timeout(asyncTimeout); 103 | 104 | }); -------------------------------------------------------------------------------- /src/model.js: -------------------------------------------------------------------------------- 1 | export class Path { 2 | constructor(listAS) { 3 | this.value = listAS; 4 | }; 5 | 6 | getFirst() { 7 | return this.value[0] ?? null; 8 | }; 9 | 10 | getLast() { 11 | return this.value[this.value.length - 1] ?? null; 12 | }; 13 | 14 | length() { 15 | return this.value.length; 16 | }; 17 | 18 | toString() { 19 | return JSON.stringify(this.toJSON()); 20 | }; 21 | 22 | getValues() { 23 | return this.value.map(i => i.getValue()); 24 | }; 25 | 26 | toJSON() { 27 | return this.getValues(); 28 | }; 29 | 30 | _hasLoop(arr) { 31 | const seen = new Set(); 32 | for (let i = 0; i < arr.length; i++) { 33 | if (seen.has(arr[i]) && arr[i] !== arr[i - 1]) { 34 | return true; 35 | } 36 | seen.add(arr[i]); 37 | } 38 | 39 | return false; 40 | } 41 | 42 | getSimplePath() { 43 | return this.value.map(i => i.numbers?.[0]).flat(); 44 | } 45 | 46 | getNeighbors(of) { 47 | const simplePath = this.getSimplePath(); 48 | 49 | if (this._hasLoop(simplePath)) { // Skip BGP loops 50 | return [null, null]; 51 | } 52 | 53 | const path = [...new Set(simplePath)].slice(1); // Remove duplicates and peer 54 | 55 | const asn = of.numbers[0]; 56 | const i = path.indexOf(asn); 57 | 58 | if (i >= 0) { 59 | 60 | const left = path?.[i - 1]; 61 | const right = path?.[i + 1]; 62 | 63 | return [left ? new AS(left) : null, right ? new AS(right) : null]; 64 | } else { 65 | return [null, null]; 66 | } 67 | }; 68 | 69 | includes(asn) { 70 | return this.value.some(i => i.includes(asn)); 71 | }; 72 | } 73 | 74 | export class AS { 75 | static _instances = {}; 76 | 77 | constructor(numbers) { 78 | this.numbers = null; 79 | this.ASset = false; 80 | this._valid = null; 81 | 82 | if (["string", "number"].includes(typeof (numbers))) { 83 | this.numbers = [numbers]; 84 | } else if (numbers instanceof Array && numbers.length) { 85 | if (numbers.length > 1) { 86 | this.ASset = true; 87 | } 88 | this.numbers = numbers; 89 | } 90 | 91 | if (this.isValid()) { 92 | this.numbers = this.numbers.map(i => parseInt(i)); 93 | 94 | const key = this.numbers.join("-"); 95 | if (!!AS._instances[key]) { 96 | return AS._instances[key]; 97 | } 98 | 99 | AS._instances[key] = this; 100 | } else { 101 | throw new Error("Not valid AS number"); 102 | } 103 | } 104 | 105 | getId() { 106 | return (this.numbers.length === 1) ? this.numbers[0] : this.numbers.sort().join("-"); 107 | }; 108 | 109 | isValid() { 110 | if (this._valid === null) { 111 | this._valid = this.numbers && 112 | this.numbers.length > 0 && 113 | this.numbers 114 | .every(asn => { 115 | 116 | try { 117 | const intAsn = parseInt(asn); 118 | if (intAsn != asn) { 119 | return false; 120 | } 121 | asn = intAsn; 122 | } catch (e) { 123 | return false; 124 | } 125 | 126 | return asn >= 0 && asn <= 4294967295; 127 | }) && 128 | [...new Set(this.numbers.map(i => parseInt(i)))].length === this.numbers.length; 129 | } 130 | 131 | return this._valid; 132 | }; 133 | 134 | includes(ASn) { 135 | return ASn.numbers.every(i => this.numbers.includes(i)); 136 | }; 137 | 138 | isASset() { 139 | return this.ASset; 140 | }; 141 | 142 | getValue() { 143 | return (this.numbers.length > 1) ? this.numbers : this.numbers[0]; 144 | }; 145 | 146 | toString() { 147 | const list = this.numbers.map(i => "AS" + i); 148 | 149 | return (list.length === 1 ? list : list.slice(0, list.length - 1).map(i => [i, ", "]).concat(["and ", list[list.length - 1]]).flat()).join(""); 150 | }; 151 | 152 | toJSON() { 153 | return this.numbers; 154 | }; 155 | } 156 | -------------------------------------------------------------------------------- /src/reports/reportKafka.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Report from "./report"; 34 | import {Kafka, logLevel} from "kafkajs"; 35 | 36 | export default class ReportKafka extends Report { 37 | 38 | constructor(channels, params, env) { 39 | super(channels, params, env); 40 | this.client = null; 41 | this.clientId = env.clientId; 42 | this.producer = null; 43 | this.connected = false; 44 | this.host = [this.params.host || "localhost", this.params.port].filter(i => i != null).join(":"); 45 | this.topics = this.params.topics; 46 | this.connecting = null; 47 | } 48 | 49 | _getTopic = (channel) => { 50 | const topic = this.topics[channel] || this.topics["default"]; 51 | if (!topic) { 52 | this.logger.log({ 53 | level: "error", 54 | message: "No topic available for alert channel: " + channel 55 | }); 56 | return false; 57 | } else { 58 | return topic; 59 | } 60 | }; 61 | 62 | _connectToKafka = () => { 63 | if (!this.connecting) { 64 | 65 | this.client = new Kafka({ 66 | logLevel: logLevel.ERROR, 67 | clientId: this.clientId, 68 | brokers: [this.host].flat() 69 | }); 70 | 71 | this.producer = this.client.producer(); 72 | 73 | this.connecting = this.producer 74 | .connect() 75 | .then(() => { 76 | this.connected = true; 77 | }) 78 | .catch((error) => { 79 | this.logger.log({ 80 | level: "error", 81 | message: "Kafka connector error: " + error 82 | }); 83 | }); 84 | } 85 | 86 | return this.connecting; 87 | }; 88 | 89 | _getPayload = (topic, channel, message) => { 90 | return { 91 | topic: topic, 92 | messages: [{value: JSON.stringify(message)}], 93 | key: channel, 94 | attributes: 1, 95 | timestamp: Date.now() 96 | }; 97 | }; 98 | 99 | report = (channel, content) => { 100 | return this._connectToKafka() 101 | .then(() => { 102 | const topic = this._getTopic(channel); 103 | return this.producer 104 | .send(this._getPayload(topic, channel, content)); 105 | }) 106 | .catch(error => { 107 | this.logger.log({ 108 | level: "error", 109 | message: error 110 | }); 111 | }); 112 | }; 113 | } -------------------------------------------------------------------------------- /src/reports/reportSyslog.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Report from "./report"; 34 | import syslog from "syslog-client"; 35 | 36 | export default class ReportSyslog extends Report { 37 | 38 | constructor(channels, params, env) { 39 | super(channels, params, env); 40 | this.client = null; 41 | this.connected = false; 42 | this.connecting = null; 43 | this.host = params.host; 44 | this.options = { 45 | syslogHostname: params.host, 46 | transport: (params.transport === "tcp") ? syslog.Transport.Tcp : syslog.Transport.Udp, 47 | port: params.port 48 | }; 49 | } 50 | 51 | _getMessage = (channel, content) => { 52 | return this.parseTemplate(this.params.templates[channel] || this.params.templates["default"], this.getContext(channel, content)); 53 | }; 54 | 55 | _connectToSyslog = () => { 56 | if (!this.connecting) { 57 | this.connecting = new Promise((resolve, reject) => { 58 | if (this.connected) { 59 | resolve(true); 60 | } else { 61 | this.client = syslog.createClient(this.host, this.options); 62 | this.connected = true; 63 | 64 | this.client.on("close", function (error) { 65 | this.logger.log({ 66 | level: "error", 67 | message: "Syslog disconnected: " + error 68 | }); 69 | }); 70 | 71 | this.client.on("error", function (error) { 72 | this.logger.log({ 73 | level: "error", 74 | message: "Syslog: " + error 75 | }); 76 | }); 77 | 78 | resolve(true); 79 | } 80 | }); 81 | } 82 | 83 | return this.connecting; 84 | }; 85 | 86 | report = (channel, content) => { 87 | return this._connectToSyslog() 88 | .then(() => { 89 | const message = this._getMessage(channel, content); 90 | 91 | this.logger.log({ 92 | level: "info", 93 | message: `[reportSyslog] sending report to: ${this.options.syslogHostname}` 94 | }); 95 | 96 | this.client.log(message, {}, error => { 97 | if (error) { 98 | this.logger.log({ 99 | level: "error", 100 | message: "Syslog: " + error 101 | }); 102 | } 103 | }); 104 | }); 105 | }; 106 | 107 | } -------------------------------------------------------------------------------- /src/monitors/monitorPathNeighbors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Monitor from "./monitor"; 34 | 35 | export default class MonitorPathNeighbors extends Monitor { 36 | 37 | constructor(name, channel, params, env, input) { 38 | super(name, channel, params, env, input); 39 | this.thresholdMinPeers = params?.thresholdMinPeers ?? 0; 40 | this.updateMonitoredResources(); 41 | }; 42 | 43 | updateMonitoredResources = () => { 44 | this.monitored = this.input.getMonitoredASns(); 45 | }; 46 | 47 | filter = (message) => { 48 | return message.type === "announcement" && this.monitored.some(i => message.path.includes(i.asn)); 49 | }; 50 | 51 | squashAlerts = (alerts) => { 52 | const peers = this.getPeers(alerts); 53 | 54 | if (peers >= this.thresholdMinPeers) { 55 | const matchedRule = alerts[0].matchedRule; 56 | const extra = alerts[0].extra; 57 | const asnText = matchedRule.asn; 58 | 59 | return `A new ${extra.side} of ${asnText} has been detected: AS${extra.neighbor}`; 60 | } 61 | 62 | return false; 63 | }; 64 | 65 | monitor = (message) => 66 | new Promise((resolve, reject) => { 67 | const path = message.path; 68 | 69 | for (let monitoredAs of this.monitored) { 70 | if (monitoredAs.upstreams !== undefined || monitoredAs.downstreams !== undefined) { 71 | const [left, right] = path.getNeighbors(monitoredAs.asn); 72 | 73 | if (left || right) { 74 | let match = false; 75 | let side = null; 76 | let id = null; 77 | 78 | if (left && (monitoredAs.upstreams !== undefined && !monitoredAs.upstreams?.includes(left))) { 79 | side = "upstream"; 80 | id = left.getId(); 81 | match = true; 82 | } 83 | 84 | if (right && (monitoredAs.downstreams !== undefined && !monitoredAs.downstreams?.includes(right))) { 85 | side = "downstream"; 86 | id = right.getId(); 87 | match = true; 88 | } 89 | 90 | if (match) { 91 | this.publishAlert([monitoredAs.asn.getId(), id].join("-"), 92 | monitoredAs.asn.getId(), 93 | monitoredAs, 94 | message, 95 | {side, neighbor: id}); 96 | } 97 | } 98 | } 99 | } 100 | 101 | resolve(true); 102 | }); 103 | 104 | } -------------------------------------------------------------------------------- /tests/rpki_tests/tests.api.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | const chai = require("chai"); 34 | const fs = require("fs"); 35 | const chaiSubset = require("chai-subset"); 36 | const expect = chai.expect; 37 | const asyncTimeout = 200000; 38 | chai.use(chaiSubset); 39 | 40 | const cacheFile = ".cache/seen-rpki-valid-announcements.json"; 41 | if (fs.existsSync(cacheFile)) { 42 | fs.unlinkSync(cacheFile); 43 | } 44 | 45 | global.EXTERNAL_CONFIG_FILE = "tests/rpki_tests/config.rpki.test.api.yml"; 46 | const worker = require("../../index"); 47 | const pubSub = worker.pubSub; 48 | 49 | 50 | describe("RPKI monitoring api", function () { 51 | 52 | it("api connector", function (done) { 53 | 54 | const expectedData = { 55 | 56 | "a103_21_244_0_24-13335-false": { 57 | id: "a103_21_244_0_24-13335-false", 58 | origin: "rpki-monitor", 59 | affected: "103.21.244.0/24", 60 | message: "The route 103.21.244.0/24 announced by AS13335 is not RPKI valid. Valid ROAs: 103.21.244.0/23|AS0|maxLength:23" 61 | }, 62 | 63 | "a8_8_8_8_22-2914-null": { 64 | id: "a8_8_8_8_22-2914-null", 65 | origin: "rpki-monitor", 66 | affected: "8.8.8.8/22", 67 | message: "The route 8.8.8.8/22 announced by AS2914 is not covered by a ROA" 68 | } 69 | }; 70 | 71 | let rpkiTestCompleted = false; 72 | let started = false; 73 | pubSub.subscribe("rpki", function (message, type) { 74 | try { 75 | if (started && !rpkiTestCompleted) { 76 | message = JSON.parse(JSON.stringify(message)); 77 | const id = message.id; 78 | 79 | expect(Object.keys(expectedData).includes(id)).to.equal(true); 80 | expect(expectedData[id] != null).to.equal(true); 81 | 82 | expect(message).to 83 | .containSubset(expectedData[id]); 84 | 85 | expect(message).to.contain 86 | .keys([ 87 | "latest", 88 | "earliest" 89 | ]); 90 | 91 | delete expectedData[id]; 92 | if (Object.keys(expectedData).length === 0) { 93 | setTimeout(() => { 94 | rpkiTestCompleted = true; 95 | done(); 96 | }, 5000); 97 | } 98 | } 99 | } catch (error) { 100 | rpkiTestCompleted = true; 101 | done(error); 102 | } 103 | }); 104 | pubSub.publish("test-type", "rpki"); 105 | started = true; 106 | }).timeout(asyncTimeout); 107 | 108 | }); -------------------------------------------------------------------------------- /tests/rpki_tests/tests.default.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | const chai = require("chai"); 34 | const fs = require("fs"); 35 | const chaiSubset = require("chai-subset"); 36 | const expect = chai.expect; 37 | const asyncTimeout = 200000; 38 | chai.use(chaiSubset); 39 | 40 | const cacheFile = ".cache/seen-rpki-valid-announcements.json"; 41 | if (fs.existsSync(cacheFile)) { 42 | fs.unlinkSync(cacheFile); 43 | } 44 | 45 | global.EXTERNAL_CONFIG_FILE = "tests/rpki_tests/config.rpki.test.default.yml"; 46 | const worker = require("../../index"); 47 | const pubSub = worker.pubSub; 48 | 49 | 50 | describe("RPKI monitoring default", function () { 51 | 52 | it("default connector", function (done) { 53 | 54 | const expectedData = { 55 | 56 | "a103_21_244_0_24-13335-false": { 57 | id: "a103_21_244_0_24-13335-false", 58 | origin: "rpki-monitor", 59 | affected: "103.21.244.0/24", 60 | message: "The route 103.21.244.0/24 announced by AS13335 is not RPKI valid. Valid ROAs: 103.21.244.0/23|AS0|maxLength:23" 61 | }, 62 | 63 | "a8_8_8_8_22-2914-null": { 64 | id: "a8_8_8_8_22-2914-null", 65 | origin: "rpki-monitor", 66 | affected: "8.8.8.8/22", 67 | message: "The route 8.8.8.8/22 announced by AS2914 is not covered by a ROA" 68 | } 69 | }; 70 | 71 | let rpkiTestCompleted = false; 72 | let started = false; 73 | pubSub.subscribe("rpki", function (message, type) { 74 | try { 75 | if (started && !rpkiTestCompleted) { 76 | message = JSON.parse(JSON.stringify(message)); 77 | const id = message.id; 78 | 79 | expect(Object.keys(expectedData).includes(id)).to.equal(true); 80 | expect(expectedData[id] != null).to.equal(true); 81 | 82 | expect(message).to 83 | .containSubset(expectedData[id]); 84 | 85 | expect(message).to.contain 86 | .keys([ 87 | "latest", 88 | "earliest" 89 | ]); 90 | 91 | delete expectedData[id]; 92 | if (Object.keys(expectedData).length === 0) { 93 | setTimeout(() => { 94 | rpkiTestCompleted = true; 95 | done(); 96 | }, 5000); 97 | } 98 | } 99 | } catch (error) { 100 | rpkiTestCompleted = true; 101 | done(error); 102 | } 103 | }); 104 | pubSub.publish("test-type", "rpki"); 105 | started = true; 106 | }).timeout(asyncTimeout); 107 | 108 | }); -------------------------------------------------------------------------------- /tests/rpki_tests/tests.external.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | const chai = require("chai"); 34 | const chaiSubset = require("chai-subset"); 35 | const fs = require("fs"); 36 | const expect = chai.expect; 37 | const asyncTimeout = 200000; 38 | chai.use(chaiSubset); 39 | 40 | global.EXTERNAL_CONFIG_FILE = "tests/rpki_tests/config.rpki.test.external.yml"; 41 | 42 | fs.copyFileSync("tests/rpki_tests/vrp.wrong.json", "tests/rpki_tests/vrp.json"); 43 | 44 | const worker = require("../../index"); 45 | const pubSub = worker.pubSub; 46 | 47 | describe("RPKI monitoring external", function () { 48 | 49 | it("external connector", function (done) { 50 | 51 | const expectedData = { 52 | 53 | "a82_112_100_0_24-2914-false": { 54 | id: "a82_112_100_0_24-2914-false", 55 | origin: "roa-monitor", 56 | affected: "82.112.100.0/24", 57 | message: "The route 82.112.100.0/24 announced by AS2914 is not RPKI valid. Valid ROAs: 82.112.100.0/24|AS1234|maxLength:24" 58 | } 59 | }; 60 | 61 | let rpkiTestCompletedExternal = false; 62 | let started = false; 63 | 64 | pubSub.subscribe("roa", function (message, type) { 65 | try { 66 | if (started && !rpkiTestCompletedExternal) { 67 | message = JSON.parse(JSON.stringify(message)); 68 | const id = message.id; 69 | 70 | expect(Object.keys(expectedData).includes(id)).to.equal(true); 71 | expect(expectedData[id] != null).to.equal(true); 72 | 73 | expect(message).to 74 | .containSubset(expectedData[id]); 75 | 76 | expect(message).to.contain 77 | .keys([ 78 | "latest", 79 | "earliest" 80 | ]); 81 | 82 | delete expectedData[id]; 83 | if (Object.keys(expectedData).length === 0) { 84 | setTimeout(() => { 85 | rpkiTestCompletedExternal = true; 86 | fs.unlinkSync("tests/rpki_tests/vrp.json"); 87 | done(); 88 | }, 8000); 89 | } 90 | } 91 | } catch (error) { 92 | rpkiTestCompletedExternal = true; 93 | done(error); 94 | } 95 | }); 96 | 97 | //Test rpki watch file reloading 98 | fs.copyFileSync("tests/rpki_tests/vrp.correct.json", "tests/rpki_tests/vrp.json"); 99 | 100 | setTimeout(() => { // Wait that the watcher realizes the file changed 101 | pubSub.publish("test-type", "rpki"); 102 | started = true; 103 | }, 16000); 104 | 105 | }).timeout(asyncTimeout); 106 | }); -------------------------------------------------------------------------------- /src/reports/reportPullAPI.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Report from "./report"; 34 | import RestApi from "../utils/restApi"; 35 | import md5 from "md5"; 36 | 37 | export default class ReportPullAPI extends Report { 38 | 39 | constructor(channels, params, env) { 40 | super(channels, params, env); 41 | 42 | this.name = "reportPullAPI" || this.params.name; 43 | this.enabled = true; 44 | this.maxAlertsAmount = Math.min(this.params.maxAlertsAmount || 25, 100); 45 | this.lastQuery = null; 46 | 47 | let restDefault = env.config.rest || {port: params.port, host: params.host}; 48 | const rest = new RestApi(restDefault); 49 | 50 | rest.addUrl("/alerts", this.respond) 51 | .catch(error => { 52 | env.logger.log({ 53 | level: "error", 54 | message: error 55 | }); 56 | }); 57 | 58 | rest.addUrl("/alerts/:hash", this.respond) 59 | .catch(error => { 60 | env.logger.log({ 61 | level: "error", 62 | message: error 63 | }); 64 | }); 65 | 66 | rest.addUrl("/alerts/groups/:group", this.respond) 67 | .catch(error => { 68 | env.logger.log({ 69 | level: "error", 70 | message: error 71 | }); 72 | }); 73 | 74 | this.alerts = []; 75 | }; 76 | 77 | respond = (req, res, next) => { 78 | res.contentType = "json"; 79 | res.send({ 80 | meta: { 81 | lastQuery: this.lastQuery 82 | }, 83 | data: this._getAlerts(req.params) 84 | }); 85 | next(); 86 | this.lastQuery = new Date().getTime(); 87 | }; 88 | 89 | _getAlerts = ({hash, group}) => { 90 | let alerts; 91 | 92 | if (group) { 93 | alerts = this.alerts.filter(i => i.group === group); 94 | } else if (hash) { 95 | alerts = this.alerts.filter(i => i.alert.hash === hash); 96 | } else { 97 | alerts = this.alerts; 98 | } 99 | 100 | return alerts.map(i => i.alert); 101 | }; 102 | 103 | getUserGroup = (group) => { 104 | return null; 105 | }; 106 | 107 | report = (channel, content) => { 108 | if (this.enabled) { 109 | let groups = content.data.map(i => i.matchedRule.group).filter(i => i != null); 110 | 111 | groups = (groups.length) ? [...new Set(groups)] : ["default"]; 112 | content.hash = md5(content.id); 113 | 114 | for (let group of groups) { 115 | this.alerts.push({ 116 | group, 117 | alert: content 118 | }); 119 | this.alerts = this.alerts.slice(-this.maxAlertsAmount); 120 | } 121 | } 122 | }; 123 | } -------------------------------------------------------------------------------- /src/reports/reportAlerta.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BSD 3-Clause License 3 | * 4 | * Copyright (c) 2019, NTT Ltd. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * Neither the name of the copyright holder nor the names of its 18 | * contributors may be used to endorse or promote products derived from 19 | * this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | import Report from "./report"; 34 | 35 | export default class ReportAlerta extends Report { 36 | 37 | constructor(channels, params, env) { 38 | super(channels, params, env); 39 | 40 | this.environment = env.config.environment; 41 | this.enabled = true; 42 | if (!this.getUserGroup("default")) { 43 | this.logger.log({ 44 | level: "error", 45 | message: "Alerta is not enabled: no default group defined" 46 | }); 47 | this.enabled = false; 48 | } 49 | 50 | this.headers = {}; 51 | if (this.params.key) { 52 | this.headers.Authorization = "Key " + this.params.key; 53 | } 54 | if (this.params.token) { 55 | this.headers.Authorization = "Bearer " + this.params.token; 56 | } 57 | 58 | } 59 | 60 | _createAlertaAlert = (url, channel, content) => { 61 | 62 | const severity = (this.params && this.params.severity && this.params.severity[channel]) 63 | ? this.params.severity[channel] 64 | : "informational"; // informational level 65 | const context = this.getContext(channel, content); 66 | 67 | if (this.params.resource_templates) { 68 | throw new Error("The resource_templates parameter has been deprecated in favour of resourceTemplates. Please update your config.yml file accordingly."); 69 | } 70 | 71 | const resource = this.params.resourceTemplates[channel] || this.params.resourceTemplates["default"]; 72 | 73 | this.axios({ 74 | url: url + "/alert", 75 | method: "POST", 76 | headers: this.headers, 77 | responseType: "json", 78 | data: { 79 | event: channel, 80 | resource: this.parseTemplate(resource, context), 81 | text: content.message, 82 | service: ["BGPalerter"], 83 | attributes: context, 84 | severity: severity, 85 | environment: this.environment 86 | } 87 | }) 88 | .catch((error) => { 89 | this.logger.log({ 90 | level: "error", 91 | message: error 92 | }); 93 | }); 94 | }; 95 | 96 | getUserGroup = (group) => { 97 | const groups = this.params.urls || this.params.userGroups; 98 | 99 | return groups[group] || groups["default"]; 100 | }; 101 | 102 | report = (channel, content) => { 103 | if (this.enabled) { 104 | let groups = content.data.map(i => i.matchedRule.group).filter(i => i != null); 105 | 106 | groups = (groups.length) ? [...new Set(groups)] : ["default"]; 107 | 108 | for (let group of groups) { 109 | const url = this.getUserGroup(group); 110 | if (url) { 111 | this._createAlertaAlert(url, channel, content); 112 | } 113 | } 114 | } 115 | }; 116 | } 117 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bgpalerter", 3 | "version": "2.0.1", 4 | "description": "BGP and RPKI monitoring tool. Pre-configured for real-time detection of visibility loss, RPKI invalid announcements, hijacks, ROA misconfiguration, and more.", 5 | "author": { 6 | "name": "Massimo Candela", 7 | "url": "https://massimocandela.com" 8 | }, 9 | "license": "BSD-3-Clause", 10 | "main": "src/worker.js", 11 | "bin": "index.js", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/nttgin/BGPalerter.git" 15 | }, 16 | "scripts": { 17 | "babel": "./node_modules/.bin/babel", 18 | "test": "rm -rf .cache/ && npm run test-core && npm run test-generate && npm run test-reports && npm run test-rpki && npm run test-neighbor", 19 | "test-core": "rm -rf volumetests/ && ./node_modules/.bin/mocha --exit tests/*.js --require @babel/register && rm -rf volumetests/", 20 | "test-reports": "./node_modules/.bin/mocha --exit tests/reports_tests/testReportSyslog.js --require @babel/register && ./node_modules/.bin/mocha --exit tests/reports_tests/testsReportHttp.js --require @babel/register", 21 | "test-generate": "./node_modules/.bin/mocha --exit tests/generate_tests/*.js --require @babel/register", 22 | "test-kafka": "./node_modules/.bin/mocha --exit tests/kafka_tests/*.js --require @babel/register", 23 | "test-neighbor": "./node_modules/.bin/mocha --exit tests/neighbor_tests/*.js --require @babel/register", 24 | "test-npm": "./node_modules/.bin/mocha --exit tests/npm_tests/*.js --require @babel/register", 25 | "test-dump": "rm -rf volumetests/ && ./node_modules/.bin/mocha --exit tests/dump_tests/*.js --require @babel/register", 26 | "test-rpki": "rm -f -R .cache/ && ./node_modules/.bin/mocha --exit tests/rpki_tests/tests.default.js --require @babel/register && ./node_modules/.bin/mocha --exit tests/rpki_tests/tests.external.js --require @babel/register && rm -f -R .cache/ && ./node_modules/.bin/mocha --exit tests/rpki_tests/tests.external-roas.js --require @babel/register && ./node_modules/.bin/mocha --exit tests/rpki_tests/tests.api.js --require @babel/register", 27 | "build": "./build.sh", 28 | "compile": "rm -rf dist/ && ./node_modules/.bin/babel index.js -d dist && ./node_modules/.bin/babel src -d dist/src && cp package.json dist/package.json && cp package-lock.json dist/package-lock.json && cp README.md dist/README.md && cp AUTHORS dist/AUTHORS", 29 | "serve": "babel-node index.js", 30 | "inspect": "node --inspect --require @babel/register index.js", 31 | "update": "git update-index --assume-unchanged config.yml && git update-index --assume-unchanged prefixes.yml && git pull", 32 | "generate-prefixes": "babel-node index.js generate" 33 | }, 34 | "keywords": [ 35 | "BGP", 36 | "monitoring", 37 | "rpki", 38 | "network", 39 | "internet", 40 | "real-time", 41 | "hijack", 42 | "detection", 43 | "measurements" 44 | ], 45 | "devDependencies": { 46 | "@babel/cli": "^7.28.0", 47 | "@babel/core": "^7.28.0", 48 | "@babel/node": "^7.28.0", 49 | "@babel/preset-env": "^7.28.0", 50 | "@yao-pkg/pkg": "^6.5.1", 51 | "chai": "^4.3.10", 52 | "chai-subset": "^1.6.0", 53 | "mocha": "^10.7.3", 54 | "read-last-lines": "^1.8.0", 55 | "syslogd": "^1.1.2" 56 | }, 57 | "dependencies": { 58 | "@sentry/node": "^9.38.0", 59 | "batch-promises": "^0.0.3", 60 | "brembo": "^2.1.5", 61 | "deepmerge": "^4.3.1", 62 | "express": "^5.1.0", 63 | "fast-file-logger": "^1.1.7", 64 | "inquirer": "=12.6.3", 65 | "ip-sub": "^1.7.1", 66 | "js-yaml": "^4.1.0", 67 | "kafkajs": "^2.2.4", 68 | "longest-prefix-match": "^1.2.10", 69 | "md5": "^2.3.0", 70 | "moment": "^2.30.1", 71 | "node-cleanup": "^2.1.2", 72 | "nodemailer": "^7.0.5", 73 | "object-fingerprint": "^1.1.2", 74 | "path": "^0.12.7", 75 | "redaxios": "^0.5.1", 76 | "rpki-validator": "^2.13.24", 77 | "semver": "^7.7.2", 78 | "syslog-client": "^1.1.1", 79 | "uuid": "^11.1.0", 80 | "ws": "^8.18.3", 81 | "yargs": "=17.7.2" 82 | }, 83 | "pkg": { 84 | "scripts": [ 85 | "./src/inputs/*.js", 86 | "./src/monitors/*.js", 87 | "./src/reports/*.js", 88 | "./src/connectors/*.js", 89 | "./src/processMonitors/*.js" 90 | ], 91 | "assets": [ 92 | "./bin/config.yml" 93 | ], 94 | "targets": [ 95 | "node22" 96 | ] 97 | }, 98 | "optionalDependencies": { 99 | "bufferutil": "^4.0.9", 100 | "utf-8-validate": "^6.0.5" 101 | } 102 | } 103 | --------------------------------------------------------------------------------