├── shared ├── __init__.py ├── hashtools.py ├── console.py ├── mactools.py ├── mltools.py ├── dnstools.py ├── .gitignore ├── iftools.py ├── api_wrapper.py └── iptools.py ├── core ├── shared ├── utils │ ├── __init__.py │ └── utils.py ├── data │ ├── plugins │ │ └── available │ │ │ ├── freebox_detector │ │ │ └── plugin.json │ │ │ └── junos_version │ │ │ └── plugin.json │ └── nltk_data │ │ └── tokenizers │ │ └── punkt │ │ └── PY3 │ │ └── english.pickle ├── config │ ├── core.conf │ ├── production.conf │ └── docker.conf ├── core.conf ├── .dockerignore ├── config.py ├── database.py └── api.py ├── sensor ├── shared ├── sensor.conf ├── setup.py ├── config.py ├── .gitignore └── sensor.py ├── tools ├── shared └── insert_fake_networks.py ├── web ├── test │ ├── actions │ │ └── .keep │ ├── sources │ │ └── .keep │ ├── stores │ │ └── .keep │ ├── loadtests.js │ ├── config │ │ └── ConfigTest.js │ ├── components │ │ ├── MainTest.js │ │ ├── HomeComponentTest.js │ │ ├── GraphComponentTest.js │ │ ├── HeaderComponentTest.js │ │ ├── LoaderComponentTest.js │ │ ├── NavbarComponentTest.js │ │ ├── SideBarComponentTest.js │ │ ├── home │ │ │ └── MetricsComponentTest.js │ │ ├── LoginPageComponentTest.js │ │ ├── SearchBarComponentTest.js │ │ ├── AddnetworkComponentTest.js │ │ ├── BreadcrumbComponentTest.js │ │ ├── HostDetailComponentTest.js │ │ ├── SensorListComponentTest.js │ │ ├── NetworkListComponentTest.js │ │ ├── settings │ │ │ ├── UserlistComponentTest.js │ │ │ └── PluginlistComponentTest.js │ │ ├── hostdetail │ │ │ └── ServicesComponentTest.js │ │ ├── modals │ │ │ └── NewNetworkComponentTest.js │ │ └── NetworkDetailComponentTest.js │ └── helpers │ │ └── shallowRenderHelper.js ├── .dockerignore ├── src │ ├── styles │ │ ├── Graph.css │ │ ├── Home.css │ │ ├── home │ │ │ └── Metrics.css │ │ ├── Breadcrumb.css │ │ ├── settings │ │ │ ├── SensorList.css │ │ │ ├── Userlist.css │ │ │ └── Pluginlist.css │ │ ├── NetworkDetail.css │ │ ├── hostdetail │ │ │ └── Services.css │ │ ├── Loader.css │ │ ├── Search.css │ │ ├── LoginPage.css │ │ ├── NetworkList.css │ │ ├── SideBar.css │ │ ├── Navbar.css │ │ ├── HostDetail.css │ │ └── App.css │ ├── favicon.ico │ ├── images │ │ └── logo.png │ ├── config │ │ ├── base.js │ │ ├── dev.js │ │ ├── dist.js │ │ ├── test.js │ │ └── README.md │ ├── index.html │ ├── lib │ │ ├── tools.js │ │ └── api.js │ ├── components │ │ ├── LoaderComponent.js │ │ ├── HeaderComponent.js │ │ ├── SidebarComponent.js │ │ ├── hostdetail │ │ │ └── ServicesComponent.js │ │ ├── settings │ │ │ ├── UserlistComponent.js │ │ │ ├── PluginlistComponent.js │ │ │ └── SensorListComponent.js │ │ ├── SearchComponent.js │ │ ├── GraphComponent.js │ │ ├── HomeComponent.js │ │ ├── home │ │ │ └── MetricsComponent.js │ │ ├── BreadcrumbComponent.js │ │ ├── NetworkDetailComponent.js │ │ ├── HostDetailComponent.js │ │ ├── modals │ │ │ └── NewNetworkComponent.js │ │ ├── LoginPageComponent.js │ │ ├── NetworkListComponent.js │ │ └── NavbarComponent.js │ ├── reducers │ │ ├── graph.js │ │ ├── users.js │ │ ├── metrics.js │ │ ├── plugins.js │ │ ├── sensors.js │ │ ├── search.js │ │ ├── hosts.js │ │ ├── sessions.js │ │ ├── networks.js │ │ └── index.js │ ├── stores │ │ └── configureStore.js │ ├── actions │ │ ├── graph.js │ │ ├── users.js │ │ ├── metrics.js │ │ ├── plugins.js │ │ ├── sensors.js │ │ ├── search.js │ │ ├── hosts.js │ │ ├── networks.js │ │ └── sessions.js │ ├── index.js │ ├── routes.js │ └── containers │ │ ├── SearchContainer.js │ │ ├── HomeContainer.js │ │ ├── NetworkListContainer.js │ │ ├── HostDetailContainer.js │ │ ├── NetworkDetailContainer.js │ │ ├── MainContainer.js │ │ └── SettingsContainer.js ├── .yo-rc.json ├── .babelrc ├── .editorconfig ├── .gitignore ├── .eslintrc ├── karma.conf.js ├── webpack.config.js ├── cfg │ ├── dev.js │ ├── dist.js │ ├── base.js │ ├── test.js │ └── defaults.js ├── server.js └── package.json ├── _config.yml ├── .gitattributes ├── doc └── stack.png ├── docker ├── dockerfile-web ├── docker-compose.yml ├── dockerfile-core └── docker-compose-all.yml ├── requirements.txt ├── LICENSE ├── .travis.yml ├── .gitignore └── README.md /shared/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/shared: -------------------------------------------------------------------------------- 1 | ../shared/ -------------------------------------------------------------------------------- /core/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sensor/shared: -------------------------------------------------------------------------------- 1 | ../shared/ -------------------------------------------------------------------------------- /tools/shared: -------------------------------------------------------------------------------- 1 | ../shared/ -------------------------------------------------------------------------------- /web/test/actions/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/test/sources/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/test/stores/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | web/test/* linguist-vendored 2 | -------------------------------------------------------------------------------- /web/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | dist 4 | -------------------------------------------------------------------------------- /web/src/styles/Graph.css: -------------------------------------------------------------------------------- 1 | .graph-component { 2 | } 3 | -------------------------------------------------------------------------------- /web/src/styles/Home.css: -------------------------------------------------------------------------------- 1 | .home-component { 2 | } 3 | -------------------------------------------------------------------------------- /web/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-react-webpack": {} 3 | } -------------------------------------------------------------------------------- /web/src/styles/home/Metrics.css: -------------------------------------------------------------------------------- 1 | .metrics-component { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /doc/stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbnk0/nmt/HEAD/doc/stack.png -------------------------------------------------------------------------------- /web/src/styles/Breadcrumb.css: -------------------------------------------------------------------------------- 1 | .breadcrumb-component { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /web/src/styles/settings/SensorList.css: -------------------------------------------------------------------------------- 1 | .sensorlist-component { 2 | } 3 | -------------------------------------------------------------------------------- /web/src/styles/NetworkDetail.css: -------------------------------------------------------------------------------- 1 | .networkdetail-component { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /web/src/styles/hostdetail/Services.css: -------------------------------------------------------------------------------- 1 | .services-component { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /web/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbnk0/nmt/HEAD/web/src/favicon.ico -------------------------------------------------------------------------------- /web/src/styles/Loader.css: -------------------------------------------------------------------------------- 1 | .loader-component { 2 | /*padding-top: 20px;*/ 3 | } 4 | -------------------------------------------------------------------------------- /web/src/styles/Search.css: -------------------------------------------------------------------------------- 1 | .search-component { 2 | /* padding-top: 20px;*/ 3 | } 4 | -------------------------------------------------------------------------------- /web/src/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbnk0/nmt/HEAD/web/src/images/logo.png -------------------------------------------------------------------------------- /web/src/styles/LoginPage.css: -------------------------------------------------------------------------------- 1 | .loginpage-component { 2 | padding-top: 10%; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /web/src/styles/NetworkList.css: -------------------------------------------------------------------------------- 1 | .networklist-component { 2 | /* padding-top: 20px;*/ 3 | } 4 | -------------------------------------------------------------------------------- /web/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0", 5 | "react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /web/src/styles/settings/Userlist.css: -------------------------------------------------------------------------------- 1 | .userlist-component { 2 | /* border: 1px dashed #f00; */ 3 | } 4 | -------------------------------------------------------------------------------- /web/src/styles/settings/Pluginlist.css: -------------------------------------------------------------------------------- 1 | .pluginlist-component { 2 | /* border: 1px dashed #f00; */ 3 | } 4 | -------------------------------------------------------------------------------- /core/data/plugins/available/freebox_detector/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1", 3 | "name": "Freebox detector" 4 | } 5 | -------------------------------------------------------------------------------- /core/data/plugins/available/junos_version/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1", 3 | "name": "Junos version detector" 4 | } 5 | -------------------------------------------------------------------------------- /shared/hashtools.py: -------------------------------------------------------------------------------- 1 | import shortuuid 2 | 3 | 4 | def make_shortuuid(string): 5 | return shortuuid.uuid(name=string) 6 | -------------------------------------------------------------------------------- /web/src/config/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Settings configured here will be merged into the final config object. 4 | export default { 5 | } 6 | -------------------------------------------------------------------------------- /core/data/nltk_data/tokenizers/punkt/PY3/english.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbnk0/nmt/HEAD/core/data/nltk_data/tokenizers/punkt/PY3/english.pickle -------------------------------------------------------------------------------- /core/config/core.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | production = 0 3 | 4 | [api] 5 | debug = 1 6 | 7 | [database] 8 | host = localhost 9 | port = 28015 10 | name = nmt -------------------------------------------------------------------------------- /core/core.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | production = 0 3 | 4 | [api] 5 | debug = 1 6 | 7 | [database] 8 | host = localhost 9 | port = 28015 10 | name = nmt 11 | -------------------------------------------------------------------------------- /core/config/production.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | production = 1 3 | 4 | [api] 5 | debug = 0 6 | 7 | [database] 8 | host = localhost 9 | port = 28015 10 | name = nmt -------------------------------------------------------------------------------- /core/config/docker.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | production = 0 3 | 4 | [api] 5 | debug = 1 6 | 7 | [database] 8 | host = rethinkdb 9 | port = 28015 10 | name = nmt 11 | -------------------------------------------------------------------------------- /sensor/sensor.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | sensor_name = sensor1 3 | 4 | [scanner] 5 | tcp_port_range = 1-1024 6 | 7 | [core_api] 8 | host = localhost 9 | port = 8080 10 | -------------------------------------------------------------------------------- /web/src/styles/SideBar.css: -------------------------------------------------------------------------------- 1 | .sidebar-component { 2 | width: 100%; 3 | } 4 | 5 | .sidebar-component .ui .vertical menu { 6 | padding-right: 0px; 7 | width: 100%; 8 | margin-right: 0px; 9 | } 10 | -------------------------------------------------------------------------------- /core/.dockerignore: -------------------------------------------------------------------------------- 1 | docker/ 2 | docker-compose.yml 3 | Dockerfile 4 | dockerfile-core 5 | dockerfile-web 6 | .dockerignore 7 | .gitignore 8 | README.md 9 | 10 | 11 | */build 12 | */node_modules 13 | 14 | -------------------------------------------------------------------------------- /docker/dockerfile-web: -------------------------------------------------------------------------------- 1 | FROM node:8 2 | ENV NPM_CONFIG_LOGLEVEL warn 3 | COPY web/ / 4 | 5 | RUN npm install 6 | RUN npm install -g webpack 7 | RUN npm install -g serve 8 | RUN npm run dist 9 | 10 | CMD serve -s dist -p 8000 11 | EXPOSE 8000 -------------------------------------------------------------------------------- /web/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /web/src/config/dev.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import baseConfig from './base'; 4 | 5 | let config = { 6 | appEnv: 'dev', // feel free to remove the appEnv property her 7 | }; 8 | 9 | export default Object.freeze(Object.assign({}, baseConfig, config)); 10 | -------------------------------------------------------------------------------- /web/src/config/dist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import baseConfig from './base'; 4 | 5 | let config = { 6 | appEnv: 'dist', // feel free to remove the appEnv property here 7 | }; 8 | 9 | export default Object.freeze(Object.assign({}, baseConfig, config)); 10 | -------------------------------------------------------------------------------- /web/test/loadtests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('babel-polyfill'); 4 | require('core-js/fn/object/assign'); 5 | 6 | // Add support for all files in the test directory 7 | const testsContext = require.context('.', true, /(Test\.js$)|(Helper\.js$)/); 8 | testsContext.keys().forEach(testsContext); 9 | -------------------------------------------------------------------------------- /web/src/styles/Navbar.css: -------------------------------------------------------------------------------- 1 | .navbar-component { 2 | /*padding-top: 5px;*/ 3 | padding-bottom: 50px; 4 | 5 | } 6 | 7 | .navbar-component .ui .menu { 8 | border-radius: 0; 9 | } 10 | 11 | .noradius { 12 | border-radius: 0; 13 | } 14 | 15 | .navbar-header { 16 | min-width: 12.85%; 17 | } 18 | -------------------------------------------------------------------------------- /web/src/config/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import baseConfig from './base'; 4 | 5 | let config = { 6 | appEnv: 'test', 7 | output: { 8 | publicPath: 'http://localhost:8000/' 9 | } 10 | // don't remove the appEnv property here 11 | }; 12 | 13 | export default Object.freeze(Object.assign(baseConfig, config)); 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Sanic_Cors==0.6.0.2 2 | attrdict==2.0.0 3 | netifaces==0.10.6 4 | nltk==3.2.5 5 | python-nmap 6 | python_json_logger==0.1.8 7 | requests==2.18.4 8 | rethinkdb==2.3.0.post6 9 | sanic==0.7.0 10 | sanic_openapi==0.4.0 11 | scapy_python3==0.21 12 | shortuuid==0.5.0 13 | termcolor==1.1.0 14 | textblob==0.13.0 15 | sanic_jwt 16 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | rethinkdb: 4 | image: "rethinkdb:latest" 5 | ports: 6 | - "28015:28015" 7 | - "9090:8080" 8 | volumes: 9 | - RethinkDATA:/data 10 | networks: 11 | - core_net 12 | 13 | volumes: 14 | RethinkDATA: 15 | driver: local 16 | 17 | networks: 18 | core_net: 19 | -------------------------------------------------------------------------------- /web/test/config/ConfigTest.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | /*global expect */ 3 | /*eslint no-console: 0*/ 4 | 'use strict'; 5 | 6 | import config from 'config'; 7 | 8 | describe('appEnvConfigTests', function () { 9 | it('should load app config file depending on current --env', function () { 10 | expect(config.appEnv).to.equal('test'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /docker/dockerfile-core: -------------------------------------------------------------------------------- 1 | FROM python:3.6 2 | RUN apt-get update -y 3 | RUN apt-get install -y python3-pip 4 | COPY /core/ /app/core/ 5 | COPY /requirements.txt /app/core/requirements.txt 6 | COPY /shared/ /app/shared/ 7 | COPY /core/config/docker.conf /app/core/core.conf 8 | WORKDIR /app/core/ 9 | RUN pip3 install -r requirements.txt 10 | CMD ["python3.6", "api.py"] 11 | 12 | -------------------------------------------------------------------------------- /web/src/styles/HostDetail.css: -------------------------------------------------------------------------------- 1 | .hostdetail-component { 2 | } 3 | 4 | .card-h { 5 | box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); 6 | transition: all 0.3s cubic-bezier(.25,.8,.25,1); 7 | border-top: 1px; 8 | border-top-color: chocolate; 9 | } 10 | 11 | .card-h:hover { 12 | box-shadow: 0 3px 5px rgba(0,0,0,0.25), 0 5px 5px rgba(0,0,0,0.22); 13 | } -------------------------------------------------------------------------------- /sensor/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | __author__ = 'NMT' 5 | 6 | setup( 7 | name='nmt-sensor', 8 | version='0.0.1', 9 | scripts=['sensor.py'], 10 | packages=['.', 'shared'], 11 | data_files=[("/etc/nmt/", 12 | ["sensor.conf"])], 13 | entry_points={ 14 | 'console_scripts': [ 15 | 'sensor = sensor:main', 16 | ], 17 | } 18 | ) -------------------------------------------------------------------------------- /core/config.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | 3 | """ Config part """ 4 | config = configparser.ConfigParser() 5 | config.read('core.conf') 6 | 7 | 8 | """ GENERAL CONFIG """ 9 | 10 | 11 | production = bool(int(config['general']['production'])) 12 | 13 | 14 | """ DATABASE CONFIG """ 15 | 16 | 17 | db_host = config['database']['host'] 18 | db_port = int(config['database']['port']) 19 | db_name = config['database']['name'] 20 | 21 | 22 | """ API CONFIG """ 23 | 24 | debug = bool(int(config['api']['debug'])) -------------------------------------------------------------------------------- /web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NMT - Network Mapper Tool 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /web/src/lib/tools.js: -------------------------------------------------------------------------------- 1 | import Moment from 'moment'; 2 | 3 | var Address4 = require('ip-address').Address4; 4 | 5 | export function formatDate(date,locale) { 6 | Moment.locale(locale); 7 | return Moment(date).format('D MMM YYYY H:m') 8 | } 9 | 10 | export function ValidateIPaddress(ipaddress) 11 | { 12 | var address = new Address4(ipaddress); 13 | return(address.isValid()) 14 | } 15 | 16 | export function renderOs(host) { 17 | var os = '-' 18 | if ('details' in host) { 19 | if ('osfamily' in host.details) { 20 | var os = host.details.osfamily + ' ' + host.details.osgen; 21 | } 22 | } 23 | return os; 24 | } -------------------------------------------------------------------------------- /shared/console.py: -------------------------------------------------------------------------------- 1 | from termcolor import colored 2 | 3 | 4 | def write(msg, status): 5 | 6 | if status == 0: 7 | color = 'green' 8 | sts = 'OK' 9 | elif status == 1: 10 | color = 'yellow' 11 | sts = 'WARN' 12 | elif status == 2: 13 | color = 'red' 14 | sts = 'FAIL' 15 | elif status == 3: 16 | color = 'white' 17 | sts = '*' 18 | elif status == 4: 19 | color = 'green' 20 | sts = '->' 21 | 22 | text = colored(sts, color) 23 | result = "[" + text + "] " + msg 24 | 25 | print(result) 26 | 27 | if __name__ == "__main__": 28 | write("Test message", 0) 29 | -------------------------------------------------------------------------------- /web/test/components/MainTest.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | /*global expect */ 3 | /*eslint no-console: 0*/ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import React from 'react/addons'; 8 | // const TestUtils = React.addons.TestUtils; 9 | import createComponent from 'helpers/shallowRenderHelper'; 10 | 11 | import Main from 'components/Main'; 12 | 13 | describe('MainComponent', function () { 14 | 15 | beforeEach(function () { 16 | this.MainComponent = createComponent(Main); 17 | }); 18 | 19 | it('should have its component name as default className', function () { 20 | expect(this.MainComponent.props.className).to.equal('index'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/HomeComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import HomeComponent from 'components//HomeComponent.js'; 11 | 12 | describe('HomeComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(HomeComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('home-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/GraphComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import GraphComponent from 'components//GraphComponent.js'; 11 | 12 | describe('GraphComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(GraphComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('graph-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/HeaderComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import HeaderComponent from 'components//HeaderComponent.js'; 11 | 12 | describe('HeaderComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(HeaderComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('header-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/LoaderComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import LoaderComponent from 'components//LoaderComponent.js'; 11 | 12 | describe('LoaderComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(LoaderComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('loader-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/NavbarComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import NavbarComponent from 'components//NavbarComponent.js'; 11 | 12 | describe('NavbarComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(NavbarComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('navbar-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/SideBarComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import SideBarComponent from 'components//SideBarComponent.js'; 11 | 12 | describe('SideBarComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(SideBarComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('sidebar-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | # Logs 3 | logs 4 | *.log 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | 30 | # Bower 31 | bower_components/ 32 | 33 | # IDE/Editor data 34 | .idea 35 | -------------------------------------------------------------------------------- /web/test/components/home/MetricsComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import MetricsComponent from 'components/home/MetricsComponent.js'; 11 | 12 | describe('MetricsComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(MetricsComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('metrics-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/LoginPageComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import LoginPageComponent from 'components//LoginPageComponent.js'; 11 | 12 | describe('LoginPageComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(LoginPageComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('loginpage-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/SearchBarComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import SearchBarComponent from 'components//SearchBarComponent.js'; 11 | 12 | describe('SearchBarComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(SearchBarComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('searchbar-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/AddnetworkComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import AddnetworkComponent from 'components//AddnetworkComponent.js'; 11 | 12 | describe('AddnetworkComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(AddnetworkComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('addnetwork-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/BreadcrumbComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import BreadcrumbComponent from 'components//BreadcrumbComponent.js'; 11 | 12 | describe('BreadcrumbComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(BreadcrumbComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('breadcrumb-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/HostDetailComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import HostDetailComponent from 'components//HostDetailComponent.js'; 11 | 12 | describe('HostDetailComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(HostDetailComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('hostdetail-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/SensorListComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import SensorListComponent from 'components//SensorListComponent.js'; 11 | 12 | describe('SensorListComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(SensorListComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('sensorlist-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/src/components/LoaderComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Dimmer, Loader, Segment } from 'semantic-ui-react' 5 | 6 | require('styles//Loader.css'); 7 | 8 | class LoaderComponent extends React.Component { 9 | render() { 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ); 21 | } 22 | } 23 | 24 | LoaderComponent.displayName = 'LoaderComponent'; 25 | 26 | // Uncomment properties you need 27 | // LoaderComponent.propTypes = {}; 28 | // LoaderComponent.defaultProps = {}; 29 | 30 | export default LoaderComponent; 31 | -------------------------------------------------------------------------------- /web/src/reducers/graph.js: -------------------------------------------------------------------------------- 1 | export function graphHasErrored(state = false, action) { 2 | switch (action.type) { 3 | case 'GRAPH_HAS_ERRORED': 4 | return action.hasErrored; 5 | 6 | default: 7 | return state; 8 | } 9 | } 10 | 11 | export function graphIsLoading(state = false, action) { 12 | switch (action.type) { 13 | case 'GRAPH_IS_LOADING': 14 | return action.isLoading; 15 | 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export function graph(state = {}, action) { 22 | switch (action.type) { 23 | case 'GRAPH_FETCH_DATA_SUCCESS': 24 | return action.graph; 25 | 26 | default: 27 | return state; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /web/src/reducers/users.js: -------------------------------------------------------------------------------- 1 | export function usersHasErrored(state = false, action) { 2 | switch (action.type) { 3 | case 'USERS_HAS_ERRORED': 4 | return action.hasErrored; 5 | 6 | default: 7 | return state; 8 | } 9 | } 10 | 11 | export function usersIsLoading(state = false, action) { 12 | switch (action.type) { 13 | case 'USERS_IS_LOADING': 14 | return action.isLoading; 15 | 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export function users(state = {}, action) { 22 | switch (action.type) { 23 | case 'USERS_FETCH_DATA_SUCCESS': 24 | return action.users; 25 | 26 | default: 27 | return state; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /web/test/components/NetworkListComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import NetworkListComponent from 'components//NetworkListComponent.js'; 11 | 12 | describe('NetworkListComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(NetworkListComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('networklist-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/settings/UserlistComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import UserlistComponent from 'components/settings/UserlistComponent.js'; 11 | 12 | describe('UserlistComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(UserlistComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('userlist-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/hostdetail/ServicesComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import ServicesComponent from 'components/hostdetail/ServicesComponent.js'; 11 | 12 | describe('ServicesComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(ServicesComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('services-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/modals/NewNetworkComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import NewNetworkComponent from 'components/modals/NewNetworkComponent.js'; 11 | 12 | describe('NewNetworkComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(NewNetworkComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('newnetwork-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/test/components/settings/PluginlistComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import PluginlistComponent from 'components/settings/PluginlistComponent.js'; 11 | 12 | describe('PluginlistComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(PluginlistComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('pluginlist-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/src/reducers/metrics.js: -------------------------------------------------------------------------------- 1 | export function metricsHasErrored(state = false, action) { 2 | switch (action.type) { 3 | case 'METRICS_HAS_ERRORED': 4 | return action.hasErrored; 5 | 6 | default: 7 | return state; 8 | } 9 | } 10 | 11 | export function metricsIsLoading(state = false, action) { 12 | switch (action.type) { 13 | case 'METRICS_IS_LOADING': 14 | return action.isLoading; 15 | 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export function metrics(state = {}, action) { 22 | switch (action.type) { 23 | case 'METRICS_FETCH_DATA_SUCCESS': 24 | return action.metrics; 25 | 26 | default: 27 | return state; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /web/src/reducers/plugins.js: -------------------------------------------------------------------------------- 1 | export function pluginsHasErrored(state = false, action) { 2 | switch (action.type) { 3 | case 'PLUGINS_HAS_ERRORED': 4 | return action.hasErrored; 5 | 6 | default: 7 | return state; 8 | } 9 | } 10 | 11 | export function pluginsIsLoading(state = false, action) { 12 | switch (action.type) { 13 | case 'PLUGINS_IS_LOADING': 14 | return action.isLoading; 15 | 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export function plugins(state = {}, action) { 22 | switch (action.type) { 23 | case 'PLUGINS_FETCH_DATA_SUCCESS': 24 | return action.plugins; 25 | 26 | default: 27 | return state; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /web/src/reducers/sensors.js: -------------------------------------------------------------------------------- 1 | export function sensorsHasErrored(state = false, action) { 2 | switch (action.type) { 3 | case 'SENSORS_HAS_ERRORED': 4 | return action.hasErrored; 5 | 6 | default: 7 | return state; 8 | } 9 | } 10 | 11 | export function sensorsIsLoading(state = false, action) { 12 | switch (action.type) { 13 | case 'SENSORS_IS_LOADING': 14 | return action.isLoading; 15 | 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export function sensors(state = {}, action) { 22 | switch (action.type) { 23 | case 'SENSORS_FETCH_DATA_SUCCESS': 24 | return action.sensors; 25 | 26 | default: 27 | return state; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /web/test/components/NetworkDetailComponentTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import NetworkDetailComponent from 'components//NetworkDetailComponent.js'; 11 | 12 | describe('NetworkDetailComponent', () => { 13 | let component; 14 | 15 | beforeEach(() => { 16 | component = createComponent(NetworkDetailComponent); 17 | }); 18 | 19 | it('should have its component name as default className', () => { 20 | expect(component.props.className).to.equal('networkdetail-component'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /web/src/stores/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import rootReducer from '../reducers'; 4 | import createHistory from 'history/createBrowserHistory' 5 | import { routerMiddleware} from 'react-router-redux' 6 | 7 | export const history = createHistory() 8 | 9 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 10 | 11 | const middleware = ( 12 | composeEnhancers( 13 | applyMiddleware(thunk), 14 | applyMiddleware(routerMiddleware(history)) 15 | ) 16 | ); 17 | 18 | 19 | export function configureStore(initialState) { 20 | return createStore( 21 | rootReducer, 22 | initialState, 23 | middleware 24 | ); 25 | } -------------------------------------------------------------------------------- /web/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": [ 4 | "react" 5 | ], 6 | "parserOptions": { 7 | "ecmaVersion": 6, 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "jsx": true 11 | } 12 | }, 13 | "env": { 14 | "browser": true, 15 | "amd": true, 16 | "es6": true, 17 | "node": true, 18 | "mocha": true 19 | }, 20 | "rules": { 21 | "comma-dangle": 1, 22 | "quotes": [ 1, "single" ], 23 | "no-undef": 1, 24 | "global-strict": 0, 25 | "no-extra-semi": 1, 26 | "no-underscore-dangle": 0, 27 | "no-console": 1, 28 | "no-unused-vars": 1, 29 | "no-trailing-spaces": [1, { "skipBlankLines": true }], 30 | "no-unreachable": 1, 31 | "no-alert": 0, 32 | "react/jsx-uses-react": 1, 33 | "react/jsx-uses-vars": 1 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /web/src/components/HeaderComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Icon, Header, Segment } from 'semantic-ui-react'; 5 | 6 | class HeaderComponent extends React.Component { 7 | render() { 8 | return ( 9 | 10 |
11 | 12 | 13 | {this.props.isLoading ? 'LOADING...' : 14 | this.props.title} 15 | 16 | {this.props.subtitle} 17 | 18 | 19 |
20 |
21 | 22 | ); 23 | } 24 | } 25 | 26 | HeaderComponent.displayName = 'HeaderComponent'; 27 | 28 | export default HeaderComponent; 29 | -------------------------------------------------------------------------------- /shared/mactools.py: -------------------------------------------------------------------------------- 1 | 2 | import pprint 3 | import requests 4 | import json 5 | import os 6 | 7 | datapath = os.path.abspath(__file__).split('/')[:-1] 8 | datapath = "/".join(datapath) + "/data/" 9 | 10 | macvendors = {} 11 | with open(datapath + 'mac_vendors.txt', 'r') as f: 12 | for line in f: 13 | line = line.strip().rstrip() 14 | line = line.split(';') 15 | mac = line[0] 16 | vendor = line[1] 17 | macvendors[mac] = vendor 18 | 19 | 20 | def get_mac_vendor(mac): 21 | result = "Unknown" 22 | 23 | # Anonymize mac 24 | mac = mac.split(':')[:3] 25 | mac = "".join(mac) 26 | 27 | mac = mac.strip(':') 28 | mac = mac.upper() 29 | 30 | if mac in macvendors.keys(): 31 | result = macvendors[mac] 32 | 33 | return result 34 | 35 | if __name__ == "__main__": 36 | print(get_mac_vendor("4c:34:88")) 37 | -------------------------------------------------------------------------------- /sensor/config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import configparser 3 | from pythonjsonlogger import jsonlogger 4 | 5 | # For hiding scapy warnings 6 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 7 | 8 | 9 | """ Logging part """ 10 | logger = logging.getLogger() 11 | 12 | logHandler = logging.StreamHandler() 13 | formatter = jsonlogger.JsonFormatter() 14 | logHandler.setFormatter(formatter) 15 | logger.addHandler(logHandler) 16 | logger.setLevel(logging.ERROR) 17 | 18 | 19 | """ Config part """ 20 | config = configparser.ConfigParser() 21 | config.read('sensor.conf') 22 | 23 | api_host = config['core_api']['host'] 24 | api_port = config['core_api']['port'] 25 | 26 | sensor_name = config['general']['sensor_name'] 27 | 28 | tcp_port_range = config['scanner']['tcp_port_range'] 29 | 30 | api_url = "http://{}:{}".format(api_host, api_port) 31 | 32 | login = 'admin' 33 | password = 'password' -------------------------------------------------------------------------------- /web/karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpackCfg = require('./webpack.config'); 2 | 3 | // Set node environment to testing 4 | process.env.NODE_ENV = 'test'; 5 | 6 | module.exports = function(config) { 7 | config.set({ 8 | basePath: '', 9 | browsers: [ 'PhantomJS' ], 10 | files: [ 11 | 'test/loadtests.js' 12 | ], 13 | port: 8000, 14 | captureTimeout: 60000, 15 | frameworks: [ 'mocha', 'chai' ], 16 | client: { 17 | mocha: {} 18 | }, 19 | singleRun: true, 20 | reporters: [ 'mocha', 'coverage' ], 21 | preprocessors: { 22 | 'test/loadtests.js': [ 'webpack', 'sourcemap' ] 23 | }, 24 | webpack: webpackCfg, 25 | webpackServer: { 26 | noInfo: true 27 | }, 28 | coverageReporter: { 29 | dir: 'coverage/', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text' } 33 | ] 34 | } 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /web/src/styles/App.css: -------------------------------------------------------------------------------- 1 | /* Base Application Styles */ 2 | body { 3 | background-color: #f2f2f2; 4 | overflow-y: scroll; 5 | } 6 | 7 | 8 | .index .notice { 9 | margin: 20px auto; 10 | padding: 15px 0; 11 | text-align: center; 12 | border: 1px solid #000; 13 | border-width: 1px 0; 14 | background: #666; 15 | } 16 | 17 | 18 | 19 | .tabs-menu { 20 | display: table; 21 | list-style: none; 22 | padding: 0; 23 | margin: 0; 24 | } 25 | 26 | .tabs-menu-item { 27 | float: left; 28 | margin-right: 20px; 29 | } 30 | 31 | .tabs-menu-item a { 32 | cursor: pointer; 33 | display: block; 34 | color: #A9A9A9; 35 | } 36 | 37 | .tabs-menu-item:not(.is-active) a:hover, 38 | .tabs-menu-item.is-active a { 39 | color: #3498DB; 40 | } 41 | 42 | .tab-panel { 43 | padding: 10px 50px; 44 | } 45 | 46 | .tabs-navigation { 47 | padding: 0 50px; 48 | } 49 | 50 | .tabs-panel { 51 | padding: 50px; 52 | } -------------------------------------------------------------------------------- /web/src/config/README.md: -------------------------------------------------------------------------------- 1 | # About this folder 2 | 3 | This folder holds configuration files for different environments. 4 | You can use it to provide your app with different settings based on the 5 | current environment, e.g. to configure different API base urls depending on 6 | whether your setup runs in dev mode or is built for distribution. 7 | You can include the configuration into your code like this: 8 | 9 | **ES2015 Modules** 10 | 11 | ```js 12 | import config from 'config'; 13 | ``` 14 | 15 | **Common JS** 16 | 17 | Due to Babel6 we need to append `.default`. 18 | 19 | ```js 20 | let config = require('config').default; 21 | ``` 22 | 23 | **Example** 24 | 25 | ```javascript 26 | import React from 'react'; 27 | import config from 'config'; 28 | 29 | class MyComponent extends React.Component { 30 | constructor(props, ctx) { 31 | super(props, ctx); 32 | let currentAppEnv = config.appEnv; 33 | } 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /docker/docker-compose-all.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | rethinkdb: 4 | container_name: rethinkdb 5 | image: "rethinkdb:latest" 6 | ports: 7 | - "28015:28015" 8 | - "9090:8080" 9 | volumes: 10 | - RethinkDATA:/data 11 | networks: 12 | - core_net 13 | 14 | core_api: 15 | container_name: coreapi 16 | build: 17 | context: ../ 18 | dockerfile: docker/dockerfile-core 19 | ports: 20 | - "8080:8080" 21 | networks: 22 | - core_net 23 | - front_net 24 | depends_on: 25 | - rethinkdb 26 | 27 | web: 28 | container_name: web 29 | build: 30 | context: ../ 31 | dockerfile: docker/dockerfile-web 32 | ports: 33 | - "8000:8000" 34 | networks: 35 | - front_net 36 | depends_on: 37 | - core_api 38 | - rethinkdb 39 | 40 | volumes: 41 | RethinkDATA: 42 | driver: local 43 | 44 | networks: 45 | core_net: 46 | front_net: 47 | -------------------------------------------------------------------------------- /web/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const args = require('minimist')(process.argv.slice(2)); 5 | 6 | // List of allowed environments 7 | const allowedEnvs = ['dev', 'dist', 'test']; 8 | 9 | // Set the correct environment 10 | let env; 11 | if (args._.length > 0 && args._.indexOf('start') !== -1) { 12 | env = 'test'; 13 | } else if (args.env) { 14 | env = args.env; 15 | } else { 16 | env = 'dev'; 17 | } 18 | process.env.REACT_WEBPACK_ENV = env; 19 | 20 | /** 21 | * Build the webpack configuration 22 | * @param {String} wantedEnv The wanted environment 23 | * @return {Object} Webpack config 24 | */ 25 | function buildConfig(wantedEnv) { 26 | let isValid = wantedEnv && wantedEnv.length > 0 && allowedEnvs.indexOf(wantedEnv) !== -1; 27 | let validEnv = isValid ? wantedEnv : 'dev'; 28 | let config = require(path.join(__dirname, 'cfg/' + validEnv)); 29 | return config; 30 | } 31 | 32 | module.exports = buildConfig(env); 33 | -------------------------------------------------------------------------------- /web/src/reducers/search.js: -------------------------------------------------------------------------------- 1 | 2 | export function searchHasErrored(state = false, action) { 3 | switch (action.type) { 4 | case 'SEARCH_HAS_ERRORED': 5 | return action.hasErrored; 6 | 7 | default: 8 | return state; 9 | } 10 | } 11 | 12 | export function searchIsLoading(state = false, action) { 13 | switch (action.type) { 14 | case 'SEARCH_IS_LOADING': 15 | return action.isLoading; 16 | 17 | default: 18 | return state; 19 | } 20 | } 21 | 22 | export function search(state = {}, action) { 23 | switch (action.type) { 24 | case 'SEARCH_FETCH_DATA_SUCCESS': 25 | return action.search; 26 | 27 | default: 28 | return state; 29 | } 30 | } 31 | 32 | 33 | export function searchterm(state = '', action) { 34 | switch (action.type) { 35 | case 'SET_SEARCH_TERM': 36 | return action.searchterm; 37 | 38 | default: 39 | return state; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /web/test/helpers/shallowRenderHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function to get the shallow output for a given component 3 | * As we are using phantom.js, we also need to include the fn.proto.bind shim! 4 | * 5 | * @see http://simonsmith.io/unit-testing-react-components-without-a-dom/ 6 | * @author somonsmith 7 | */ 8 | import React from 'react'; 9 | import TestUtils from 'react-addons-test-utils'; 10 | 11 | /** 12 | * Get the shallow rendered component 13 | * 14 | * @param {Object} component The component to return the output for 15 | * @param {Object} props [optional] The components properties 16 | * @param {Mixed} ...children [optional] List of children 17 | * @return {Object} Shallow rendered output 18 | */ 19 | export default function createComponent(component, props = {}, ...children) { 20 | const shallowRenderer = TestUtils.createRenderer(); 21 | shallowRenderer.render(React.createElement(component, props, children.length > 1 ? children : children[0])); 22 | return shallowRenderer.getRenderOutput(); 23 | } 24 | -------------------------------------------------------------------------------- /web/src/actions/graph.js: -------------------------------------------------------------------------------- 1 | import { Api } from '../lib/api.js' 2 | 3 | export function graphHasErrored(bool) { 4 | return { 5 | type: 'GRAPH_HAS_ERRORED', 6 | hasErrored: bool 7 | }; 8 | } 9 | 10 | export function graphIsLoading(bool) { 11 | return { 12 | type: 'GRAPH_IS_LOADING', 13 | isLoading: bool 14 | }; 15 | } 16 | 17 | export function graphFetchDataSuccess(graph) { 18 | return { 19 | type: 'GRAPH_FETCH_DATA_SUCCESS', 20 | graph 21 | }; 22 | } 23 | 24 | 25 | export function graphFetchData(url) { 26 | return (dispatch) => { 27 | dispatch(graphIsLoading(true)); 28 | 29 | Api.get(url) 30 | .then((response) => { 31 | dispatch(graphIsLoading(false)); 32 | return response; 33 | }) 34 | .then((response) => response.json()) 35 | .then((graph) => dispatch(graphFetchDataSuccess(graph))) 36 | .catch(() => dispatch(graphHasErrored(true))); 37 | }; 38 | } -------------------------------------------------------------------------------- /web/src/actions/users.js: -------------------------------------------------------------------------------- 1 | import { Api } from '../lib/api.js' 2 | 3 | export function usersHasErrored(bool) { 4 | return { 5 | type: 'USERS_HAS_ERRORED', 6 | hasErrored: bool 7 | }; 8 | } 9 | 10 | export function usersIsLoading(bool) { 11 | return { 12 | type: 'USERS_IS_LOADING', 13 | isLoading: bool 14 | }; 15 | } 16 | 17 | export function usersFetchDataSuccess(users) { 18 | return { 19 | type: 'USERS_FETCH_DATA_SUCCESS', 20 | users 21 | }; 22 | } 23 | 24 | 25 | export function usersFetchData(url) { 26 | return (dispatch) => { 27 | 28 | dispatch(usersIsLoading(true)); 29 | 30 | Api.get(url) 31 | .then((response) => { 32 | dispatch(usersIsLoading(false)); 33 | 34 | return response; 35 | }) 36 | .then((response) => response.json()) 37 | .then((users) => dispatch(usersFetchDataSuccess(users))) 38 | .catch(() => dispatch(usersHasErrored(true))); 39 | }; 40 | } -------------------------------------------------------------------------------- /web/src/actions/metrics.js: -------------------------------------------------------------------------------- 1 | import { Api } from '../lib/api.js' 2 | 3 | export function metricsHasErrored(bool) { 4 | return { 5 | type: 'METRICS_HAS_ERRORED', 6 | hasErrored: bool 7 | }; 8 | } 9 | 10 | export function metricsIsLoading(bool) { 11 | return { 12 | type: 'METRICS_IS_LOADING', 13 | isLoading: bool 14 | }; 15 | } 16 | 17 | export function metricsFetchDataSuccess(metrics) { 18 | return { 19 | type: 'METRICS_FETCH_DATA_SUCCESS', 20 | metrics 21 | }; 22 | } 23 | 24 | 25 | export function metricsFetchData(url) { 26 | return (dispatch) => { 27 | dispatch(metricsIsLoading(true)); 28 | 29 | Api.get(url) 30 | .then((response) => { 31 | dispatch(metricsIsLoading(false)); 32 | return response; 33 | }) 34 | .then((response) => response.json()) 35 | .then((metrics) => dispatch(metricsFetchDataSuccess(metrics))) 36 | .catch(() => dispatch(metricsHasErrored(true))); 37 | }; 38 | } -------------------------------------------------------------------------------- /web/src/components/SidebarComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Menu, Label, Input } from 'semantic-ui-react' 5 | 6 | 7 | require('styles//SideBar.css'); 8 | 9 | class SidebarComponent extends React.Component { 10 | state = { activeItem: 'home' } 11 | 12 | handleItemClick = (e, { name }) => this.setState({ activeItem: name }) 13 | 14 | render() { 15 | const { activeItem } = this.state 16 | return ( 17 |
18 | 19 | 20 | 21 | Default entity 22 | 23 | 24 |
25 | ); 26 | } 27 | } 28 | 29 | SidebarComponent.displayName = 'SidebarComponent'; 30 | 31 | // Uncomment properties you need 32 | // SideBarComponent.propTypes = {}; 33 | // SideBarComponent.defaultProps = {}; 34 | 35 | export default SidebarComponent; 36 | -------------------------------------------------------------------------------- /web/src/actions/plugins.js: -------------------------------------------------------------------------------- 1 | import { Api } from '../lib/api.js' 2 | 3 | export function pluginsHasErrored(bool) { 4 | return { 5 | type: 'PLUGINS_HAS_ERRORED', 6 | hasErrored: bool 7 | }; 8 | } 9 | 10 | export function pluginsIsLoading(bool) { 11 | return { 12 | type: 'PLUGINS_IS_LOADING', 13 | isLoading: bool 14 | }; 15 | } 16 | 17 | export function pluginsFetchDataSuccess(plugins) { 18 | return { 19 | type: 'PLUGINS_FETCH_DATA_SUCCESS', 20 | plugins 21 | }; 22 | } 23 | 24 | 25 | export function pluginsFetchData(url) { 26 | return (dispatch) => { 27 | 28 | dispatch(pluginsIsLoading(true)); 29 | 30 | Api.get(url) 31 | .then((response) => { 32 | dispatch(pluginsIsLoading(false)); 33 | 34 | return response; 35 | }) 36 | .then((response) => response.json()) 37 | .then((plugins) => dispatch(pluginsFetchDataSuccess(plugins))) 38 | .catch(() => dispatch(pluginsHasErrored(true))); 39 | }; 40 | } -------------------------------------------------------------------------------- /web/src/actions/sensors.js: -------------------------------------------------------------------------------- 1 | import { Api } from '../lib/api.js' 2 | 3 | export function sensorsHasErrored(bool) { 4 | return { 5 | type: 'SENSORS_HAS_ERRORED', 6 | hasErrored: bool 7 | }; 8 | } 9 | 10 | export function sensorsIsLoading(bool) { 11 | return { 12 | type: 'SENSORS_IS_LOADING', 13 | isLoading: bool 14 | }; 15 | } 16 | 17 | export function sensorsFetchDataSuccess(sensors) { 18 | return { 19 | type: 'SENSORS_FETCH_DATA_SUCCESS', 20 | sensors 21 | }; 22 | } 23 | 24 | 25 | export function sensorsFetchData(url) { 26 | return (dispatch) => { 27 | 28 | dispatch(sensorsIsLoading(true)); 29 | 30 | Api.get(url) 31 | .then((response) => { 32 | dispatch(sensorsIsLoading(false)); 33 | 34 | return response; 35 | }) 36 | .then((response) => response.json()) 37 | .then((sensors) => dispatch(sensorsFetchDataSuccess(sensors))) 38 | .catch(() => dispatch(sensorsHasErrored(true))); 39 | }; 40 | } -------------------------------------------------------------------------------- /shared/mltools.py: -------------------------------------------------------------------------------- 1 | """ ML TOOLS """ 2 | 3 | import os 4 | import sys 5 | import nltk 6 | from textblob.classifiers import NaiveBayesClassifier 7 | 8 | pathname = os.path.dirname(sys.argv[0]) 9 | fullpath = os.path.abspath(pathname) 10 | nltk.data.path.append(fullpath + "/data/nltk_data") 11 | 12 | train = [ 13 | ('.254', 'gateway'), 14 | ('.1', 'gateway'), 15 | ('.2', 'host'), 16 | ('.3', 'host'), 17 | ('.78', 'host'), 18 | ] 19 | 20 | 21 | # WIP: CHECK FORWARDING STATE 22 | cl = NaiveBayesClassifier(train) 23 | 24 | 25 | def is_gateway(host): 26 | result = False 27 | 28 | tests = 1 29 | proba = 0 30 | 31 | splitted_ip = host['ip_addr'].split('.')[-1] 32 | # print(splitted_ip) 33 | prob_dist = cl.prob_classify(splitted_ip) 34 | # print(prob_dist.max()) 35 | # print(round(prob_dist.prob("gateway"), 2)) 36 | if cl.classify(splitted_ip) == 'gateway': 37 | result = True 38 | 39 | return result 40 | 41 | if __name__ == "__main__": 42 | hostest = {'ip_addr': "192.168.56.3"} 43 | print(is_gateway(hostest)) 44 | -------------------------------------------------------------------------------- /web/cfg/dev.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let path = require('path'); 4 | let webpack = require('webpack'); 5 | let baseConfig = require('./base'); 6 | let defaultSettings = require('./defaults'); 7 | 8 | // Add needed plugins here 9 | let BowerWebpackPlugin = require('bower-webpack-plugin'); 10 | 11 | let config = Object.assign({}, baseConfig, { 12 | entry: [ 13 | 'webpack-dev-server/client?http://127.0.0.1:' + defaultSettings.port, 14 | 'webpack/hot/only-dev-server', 15 | './src/index' 16 | ], 17 | cache: true, 18 | devtool: 'eval-source-map', 19 | plugins: [ 20 | new webpack.HotModuleReplacementPlugin(), 21 | new webpack.NoErrorsPlugin(), 22 | new BowerWebpackPlugin({ 23 | searchResolveModulesDirectories: false 24 | }) 25 | ], 26 | module: defaultSettings.getDefaultModules() 27 | }); 28 | 29 | // Add needed loaders to the defaults here 30 | config.module.loaders.push({ 31 | test: /\.(js|jsx)$/, 32 | loader: 'react-hot!babel-loader', 33 | include: [].concat( 34 | config.additionalPaths, 35 | [ path.join(__dirname, '/../src') ] 36 | ) 37 | }); 38 | 39 | module.exports = config; 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 gbnk0 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /web/src/components/hostdetail/ServicesComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Label, List, Icon } from 'semantic-ui-react' 5 | 6 | require('styles/hostdetail/Services.css'); 7 | 8 | class ServicesComponent extends React.Component { 9 | render() { 10 | return ( 11 |
12 | 13 | {this.props.host.host.services.map((proto) => ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | {proto.ports.map(port => 21 | 22 | )} 23 | 24 | 25 | 26 | 27 | ))} 28 | 29 |
30 | ); 31 | } 32 | } 33 | 34 | ServicesComponent.displayName = 'HostdetailServicesComponent'; 35 | 36 | // Uncomment properties you need 37 | // ServicesComponent.propTypes = {}; 38 | // ServicesComponent.defaultProps = {}; 39 | 40 | export default ServicesComponent; 41 | -------------------------------------------------------------------------------- /web/src/actions/search.js: -------------------------------------------------------------------------------- 1 | import { Api } from '../lib/api.js' 2 | 3 | export function searchHasErrored(bool) { 4 | return { 5 | type: 'SEARCH_HAS_ERRORED', 6 | hasErrored: bool 7 | }; 8 | } 9 | 10 | export function searchIsLoading(bool) { 11 | return { 12 | type: 'SEARCH_IS_LOADING', 13 | isLoading: bool 14 | }; 15 | } 16 | 17 | export function searchFetchDataSuccess(search) { 18 | return { 19 | type: 'SEARCH_FETCH_DATA_SUCCESS', 20 | search 21 | }; 22 | } 23 | 24 | 25 | export function setSearchTerm(searchterm) { 26 | return { 27 | type: 'SET_SEARCH_TERM', 28 | searchterm 29 | }; 30 | } 31 | 32 | export function searchFetchData(url) { 33 | return (dispatch) => { 34 | dispatch(searchIsLoading(true)); 35 | 36 | Api.get(url) 37 | .then((response) => { 38 | dispatch(searchIsLoading(false)); 39 | return response; 40 | }) 41 | .then((response) => response.json()) 42 | .then((search) => dispatch(searchFetchDataSuccess(search))) 43 | .catch(() => dispatch(searchHasErrored(true))); 44 | }; 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /shared/dnstools.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import subprocess 3 | 4 | socket.setdefaulttimeout(2) 5 | 6 | 7 | def mdns_reverse(ip_addr): 8 | try: 9 | cmd = "/usr/bin/dig +time=2 -x {} @224.0.0.251 -p 5353".format(ip_addr) 10 | response = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True) 11 | response = str(response.stdout) 12 | response = response.split('PTR')[2].split(';')[0] 13 | response = response.strip('\\t').strip('\\n') 14 | response = response[:-1].lower() 15 | except: 16 | response = "" 17 | pass 18 | 19 | return response 20 | 21 | 22 | # DNS Resolver 23 | def reverse_lookup(ip_addr): 24 | result = "" 25 | try: 26 | result = socket.gethostbyaddr(ip_addr)[0] 27 | 28 | except: 29 | result = mdns_reverse(ip_addr) 30 | pass 31 | 32 | result = "".join(result) 33 | 34 | if len(result) == 0: 35 | result = "Unknown" 36 | 37 | return str(result) 38 | 39 | 40 | # Get domain name from fqdn 41 | def get_domain_name(fqdn): 42 | domain_name = "" 43 | try: 44 | domain_name = fqdn.split('.')[1:] 45 | domain_name = ".".join(domain_name) 46 | except: 47 | pass 48 | 49 | return domain_name 50 | -------------------------------------------------------------------------------- /web/cfg/dist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let path = require('path'); 4 | let webpack = require('webpack'); 5 | 6 | let baseConfig = require('./base'); 7 | let defaultSettings = require('./defaults'); 8 | 9 | // Add needed plugins here 10 | let BowerWebpackPlugin = require('bower-webpack-plugin'); 11 | 12 | let config = Object.assign({}, baseConfig, { 13 | entry: path.join(__dirname, '../src/index'), 14 | cache: false, 15 | devtool: 'sourcemap', 16 | plugins: [ 17 | new webpack.optimize.DedupePlugin(), 18 | new webpack.DefinePlugin({ 19 | 'process.env.NODE_ENV': '"production"' 20 | }), 21 | new BowerWebpackPlugin({ 22 | searchResolveModulesDirectories: false 23 | }), 24 | new webpack.optimize.UglifyJsPlugin(), 25 | new webpack.optimize.OccurenceOrderPlugin(), 26 | new webpack.optimize.AggressiveMergingPlugin(), 27 | new webpack.NoErrorsPlugin() 28 | ], 29 | module: defaultSettings.getDefaultModules() 30 | }); 31 | 32 | // Add needed loaders to the defaults here 33 | config.module.loaders.push({ 34 | test: /\.(js|jsx)$/, 35 | loader: 'babel', 36 | include: [].concat( 37 | config.additionalPaths, 38 | [ path.join(__dirname, '/../src') ] 39 | ) 40 | }); 41 | 42 | module.exports = config; 43 | -------------------------------------------------------------------------------- /core/utils/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from ipaddress import IPv4Network 4 | 5 | 6 | def is_private(string): 7 | """ Check if a network address is private or public """ 8 | result = False 9 | result = IPv4Network(string, strict=False).is_private 10 | 11 | return result 12 | 13 | 14 | 15 | """ PLUGINS """ 16 | 17 | def get_plugin_info(plugin_path): 18 | 19 | infos = { 20 | "version": "Unknown" 21 | } 22 | 23 | try: 24 | with open(plugin_path + '/plugin.json', 'r') as info_file: 25 | infos = json.load(info_file) 26 | except: 27 | pass 28 | 29 | return infos 30 | 31 | 32 | def list_plugins(path): 33 | """ list all plugins """ 34 | plugins = [] 35 | available_path = path + '/available/' 36 | for root, dirs, files in os.walk(available_path): 37 | for filename in dirs: 38 | plugin_path = available_path + filename 39 | plugin_info = get_plugin_info(plugin_path) 40 | 41 | plugin = { 42 | "path": plugin_path, 43 | } 44 | 45 | for k,v in plugin_info.items(): 46 | plugin[k] = v 47 | 48 | plugins.append(plugin) 49 | 50 | return plugins 51 | 52 | 53 | -------------------------------------------------------------------------------- /web/server.js: -------------------------------------------------------------------------------- 1 | /*eslint no-console:0 */ 2 | 'use strict'; 3 | require('core-js/fn/object/assign'); 4 | const webpack = require('webpack'); 5 | const WebpackDevServer = require('webpack-dev-server'); 6 | const config = require('./webpack.config'); 7 | const open = require('open'); 8 | 9 | /** 10 | * Flag indicating whether webpack compiled for the first time. 11 | * @type {boolean} 12 | */ 13 | let isInitialCompilation = true; 14 | 15 | const compiler = webpack(config); 16 | 17 | new WebpackDevServer(compiler, config.devServer) 18 | .listen(config.port, 'localhost', (err) => { 19 | if (err) { 20 | console.log(err); 21 | } 22 | console.log('Listening at localhost:' + config.port); 23 | }); 24 | 25 | compiler.plugin('done', () => { 26 | if (isInitialCompilation) { 27 | // Ensures that we log after webpack printed its stats (is there a better way?) 28 | setTimeout(() => { 29 | console.log('\n✓ The bundle is now ready for serving!\n'); 30 | console.log(' Open in iframe mode:\t\x1b[33m%s\x1b[0m', 'http://localhost:' + config.port + '/webpack-dev-server/'); 31 | console.log(' Open in inline mode:\t\x1b[33m%s\x1b[0m', 'http://localhost:' + config.port + '/\n'); 32 | console.log(' \x1b[33mHMR is active\x1b[0m. The bundle will automatically rebuild and live-update on changes.') 33 | }, 350); 34 | } 35 | isInitialCompilation = false; 36 | }); 37 | -------------------------------------------------------------------------------- /web/src/components/settings/UserlistComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Table, Icon } from 'semantic-ui-react' 5 | 6 | require('styles/settings/Userlist.css'); 7 | 8 | class UserlistComponent extends React.Component { 9 | render() { 10 | return ( 11 |
12 | 13 | 14 | 15 | ID 16 | Username 17 | 18 | 19 | 20 | 21 | {this.props.users.results.map((user) => ( 22 | 23 | 24 | {user.id} 25 | 26 | 27 | {user.email} 28 | 29 | 30 | ))} 31 | 32 |
33 |
34 | ); 35 | } 36 | } 37 | 38 | UserlistComponent.displayName = 'SettingsUserlistComponent'; 39 | 40 | // Uncomment properties you need 41 | // UserlistComponent.propTypes = {}; 42 | // UserlistComponent.defaultProps = {}; 43 | 44 | export default UserlistComponent; 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | include: 3 | - stage: Tests 4 | language: python 5 | python: 3.5 6 | install: 7 | - pip3.5 install python-coveralls 8 | - pip3.5 install coverage 9 | - pip3.5 install -r requirements.txt 10 | script: 11 | - coverage run -a shared/iftools.py 12 | - coverage run -a shared/api_wrapper.py 13 | - coverage run -a shared/dnstools.py 14 | - coverage report 15 | after_success: 16 | - coveralls 17 | 18 | - stage: build-image-api 19 | script: 20 | - cp -rfp requirements.txt core/. 21 | - docker build -t $DOCKER_USER/nmt-api -f docker/dockerfile-core . 22 | 23 | after_success: 24 | - docker login -u $DOCKER_USER -p $DOCKER_PASS 25 | - docker tag $DOCKER_USER/nmt-api $DOCKER_USER/nmt-api:$TRAVIS_BUILD_NUMBER; 26 | - docker push $DOCKER_USER:$TRAVIS_BUILD_NUMBER; 27 | - docker push $DOCKER_USER/nmt-api:latest 28 | 29 | 30 | - stage: build-image-ui 31 | script: 32 | - docker build -t $DOCKER_USER/nmt-ui -f docker/dockerfile-web . 33 | 34 | after_success: 35 | - docker login -u $DOCKER_USER -p $DOCKER_PASS 36 | - docker tag $DOCKER_USER/nmt-ui $DOCKER_USER/nmt-ui:$TRAVIS_BUILD_NUMBER; 37 | - docker push $DOCKER_USER:$TRAVIS_BUILD_NUMBER; 38 | - docker push $DOCKER_USER/nmt-ui:latest 39 | 40 | -------------------------------------------------------------------------------- /web/src/index.js: -------------------------------------------------------------------------------- 1 | import 'core-js/fn/object/assign'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import 'semantic-ui-css/semantic.min.css'; 5 | 6 | import MainContainer from './containers/MainContainer'; 7 | 8 | import { Provider } from 'react-redux'; 9 | import { configureStore, history } from './stores/configureStore'; 10 | import { ConnectedRouter} from 'react-router-redux' 11 | 12 | 13 | 14 | export const initialState = { 15 | networks: { 16 | results: [], 17 | checked:[] 18 | }, 19 | hosts: { 20 | results: [], 21 | network: {} 22 | }, 23 | host: { 24 | host: { 25 | network: {}, 26 | services: [] 27 | } 28 | }, 29 | metrics: {}, 30 | users: { 31 | results: [] 32 | }, 33 | graph: { 34 | nodes: [], 35 | edges: [] 36 | }, 37 | sensors: { 38 | results: [] 39 | }, 40 | plugins: { 41 | results: [] 42 | }, 43 | search: { 44 | results: [] 45 | }, 46 | searchterm: '', 47 | sessionReducer: !!localStorage.jwt, 48 | user: { 49 | me: {} 50 | } 51 | 52 | }; 53 | const store = configureStore(initialState, history); 54 | 55 | 56 | ReactDOM.render(( 57 | 58 | 59 | 60 | 61 | 62 | ), document.getElementById('app')); -------------------------------------------------------------------------------- /web/src/reducers/hosts.js: -------------------------------------------------------------------------------- 1 | export function hostsHasErrored(state = false, action) { 2 | switch (action.type) { 3 | case 'HOSTS_HAS_ERRORED': 4 | return action.hasErrored; 5 | 6 | default: 7 | return state; 8 | } 9 | } 10 | 11 | export function hostsIsLoading(state = false, action) { 12 | switch (action.type) { 13 | case 'HOSTS_IS_LOADING': 14 | return action.isLoading; 15 | 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export function hosts(state = {}, action) { 22 | switch (action.type) { 23 | case 'HOSTS_FETCH_DATA_SUCCESS': 24 | return action.hosts; 25 | 26 | default: 27 | return state; 28 | } 29 | } 30 | 31 | export function hostHasErrored(state = false, action) { 32 | switch (action.type) { 33 | case 'HOST_HAS_ERRORED': 34 | return action.hasErrored; 35 | 36 | default: 37 | return state; 38 | } 39 | } 40 | 41 | export function hostIsLoading(state = false, action) { 42 | switch (action.type) { 43 | case 'HOST_IS_LOADING': 44 | return action.isLoading; 45 | 46 | default: 47 | return state; 48 | } 49 | } 50 | 51 | export function host(state = {}, action) { 52 | switch (action.type) { 53 | case 'HOST_FETCH_DATA_SUCCESS': 54 | return action.host; 55 | 56 | default: 57 | return state; 58 | } 59 | } -------------------------------------------------------------------------------- /web/cfg/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let path = require('path'); 3 | let defaultSettings = require('./defaults'); 4 | 5 | // Additional npm or bower modules to include in builds 6 | // Add all foreign plugins you may need into this array 7 | // @example: 8 | // let npmBase = path.join(__dirname, '../node_modules'); 9 | // let additionalPaths = [ path.join(npmBase, 'react-bootstrap') ]; 10 | let additionalPaths = []; 11 | 12 | module.exports = { 13 | additionalPaths: additionalPaths, 14 | port: defaultSettings.port, 15 | debug: true, 16 | devtool: 'eval', 17 | output: { 18 | path: path.join(__dirname, '/../dist/assets'), 19 | filename: 'app.js', 20 | publicPath: defaultSettings.publicPath 21 | }, 22 | devServer: { 23 | contentBase: './src/', 24 | historyApiFallback: true, 25 | hot: true, 26 | port: defaultSettings.port, 27 | publicPath: defaultSettings.publicPath, 28 | noInfo: false 29 | }, 30 | resolve: { 31 | extensions: ['', '.js', '.jsx'], 32 | alias: { 33 | actions: `${defaultSettings.srcPath}/actions/`, 34 | components: `${defaultSettings.srcPath}/components/`, 35 | sources: `${defaultSettings.srcPath}/sources/`, 36 | stores: `${defaultSettings.srcPath}/stores/`, 37 | styles: `${defaultSettings.srcPath}/styles/`, 38 | config: `${defaultSettings.srcPath}/config/` + process.env.REACT_WEBPACK_ENV, 39 | 'react/lib/ReactMount': 'react-dom/lib/ReactMount' 40 | } 41 | }, 42 | module: {} 43 | }; 44 | -------------------------------------------------------------------------------- /web/src/components/SearchComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | 5 | import { Icon, Table } from 'semantic-ui-react'; 6 | 7 | 8 | 9 | require('styles//Search.css'); 10 | 11 | 12 | class SearchComponent extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | } 16 | 17 | 18 | render() { 19 | let content; 20 | 21 | if (this.props.hasErrored) { 22 | content =

API Unavailable

; 23 | return content; 24 | } 25 | 26 | return ( 27 |
28 | 29 | 30 | {this.props.search.results.map((result) => ( 31 | 32 | 33 | 34 | 35 | {result.name} 36 | {result.ip_addr} 37 | {result.dns_names} 38 | 39 | {result.host_count} Hosts 40 | 41 | ))} 42 | 43 |
44 |
45 | ); 46 | } 47 | } 48 | 49 | SearchComponent.propTypes = { 50 | 51 | }; 52 | 53 | 54 | SearchComponent.displayName = 'SearchComponent'; 55 | 56 | export default SearchComponent; 57 | -------------------------------------------------------------------------------- /web/src/components/GraphComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import Graph from 'react-graph-vis'; 5 | 6 | require('styles//Graph.css'); 7 | 8 | 9 | const options = { 10 | nodes: { 11 | shape: 'dot', 12 | size: 10, 13 | font: { 14 | size: 10 15 | }, 16 | shapeProperties: { 17 | interpolation: false 18 | }, 19 | shadow:true 20 | }, 21 | edges: { 22 | width: 1, 23 | shadow:true 24 | }, 25 | physics:{ 26 | stabilization: false 27 | }, 28 | autoResize: true 29 | }; 30 | 31 | const events = { 32 | // select: function(event) { 33 | // // var { nodes, edges } = event; 34 | // // console.log('Selected nodes:'); 35 | // // console.log(nodes); 36 | // // console.log('Selected edges:'); 37 | // // console.log(edges); 38 | // } 39 | 40 | }; 41 | 42 | class GraphComponent extends React.Component { 43 | 44 | componentDidMount(){ 45 | this.network.fit(); 46 | 47 | } 48 | 49 | setNetworkInstance = nw => { 50 | this.network = nw; 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 | 63 |
64 | ); 65 | } 66 | } 67 | 68 | GraphComponent.displayName = 'GraphComponent'; 69 | 70 | 71 | export default GraphComponent; 72 | -------------------------------------------------------------------------------- /web/src/components/HomeComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import MetricsComponent from './home/MetricsComponent' 5 | import GraphComponent from './GraphComponent'; 6 | import { Header, Segment } from 'semantic-ui-react'; 7 | import { Grid } from 'semantic-ui-react' 8 | 9 | require('styles//Home.css'); 10 | 11 | class HomeComponent extends React.Component { 12 | 13 | render() { 14 | let content; 15 | 16 | if (this.props.metricshasErrored) { 17 | content =

API Unavailable

; 18 | return content; 19 | } 20 | 21 | 22 | return ( 23 |
24 | 25 | 26 |
Statistics
27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
Graph
37 | 38 |
39 |
40 | 41 | 42 | 43 |
44 |
45 | ); 46 | } 47 | } 48 | 49 | 50 | 51 | HomeComponent.propTypes = { 52 | 53 | 54 | }; 55 | 56 | 57 | HomeComponent.displayName = 'HomeComponent'; 58 | 59 | export default HomeComponent; 60 | -------------------------------------------------------------------------------- /web/src/components/home/MetricsComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Statistic } from 'semantic-ui-react' 5 | 6 | require('styles/home/Metrics.css'); 7 | 8 | class MetricsComponent extends React.Component { 9 | render() { 10 | return ( 11 |
12 | 13 | 14 | {this.props.metrics.network_count} 15 | Networks 16 | 17 | 18 | {this.props.metrics.host_count} 19 | Hosts 20 | 21 | 22 | {this.props.metrics.sensor_count} 23 | Sensors 24 | 25 | 26 | {this.props.metrics.ports_count} 27 | Open ports 28 | 29 | 30 |
31 | ); 32 | } 33 | } 34 | 35 | MetricsComponent.displayName = 'HomeMetricsComponent'; 36 | 37 | // Uncomment properties you need 38 | // MetricsComponent.propTypes = {}; 39 | // MetricsComponent.defaultProps = {}; 40 | 41 | export default MetricsComponent; 42 | -------------------------------------------------------------------------------- /web/src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Redirect, Switch } from 'react-router'; 3 | 4 | import HomeContainer from './containers/HomeContainer' 5 | import NetworkListContainer from './containers/NetworkListContainer'; 6 | import NetworkDetailContainer from './containers/NetworkDetailContainer' 7 | import HostDetailContainer from './containers/HostDetailContainer' 8 | import SearchContainer from './containers/SearchContainer' 9 | import LoginPageComponent from './components/LoginPageComponent.js' 10 | import SettingsContainer from './containers/SettingsContainer' 11 | 12 | 13 | 14 | 15 | 16 | class Routes extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | } 20 | 21 | 22 | 23 | render() { 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | 39 | 40 | } 41 | 42 | 43 | export default Routes -------------------------------------------------------------------------------- /web/src/reducers/sessions.js: -------------------------------------------------------------------------------- 1 | 2 | export function loginFailed(state = false, action) { 3 | switch (action.type) { 4 | case 'LOG_IN_FAILED': 5 | return action.isFailed; 6 | 7 | default: 8 | return state; 9 | } 10 | } 11 | 12 | export function loginLoading(state = false, action) { 13 | switch (action.type) { 14 | case 'LOG_IN_LOADING': 15 | return action.isLoading; 16 | 17 | default: 18 | return state; 19 | } 20 | } 21 | 22 | 23 | export function sessionReducer(state = '', action) { 24 | switch(action.type) { 25 | 26 | case 'LOG_IN_SUCCESS': 27 | return !!localStorage.jwt 28 | 29 | case 'LOG_OUT': 30 | return !!localStorage.jwt 31 | 32 | default: 33 | return state; 34 | } 35 | } 36 | 37 | 38 | 39 | export function userHasErrored(state = false, action) { 40 | switch (action.type) { 41 | case 'USER_HAS_ERRORED': 42 | return action.hasErrored; 43 | 44 | default: 45 | return state; 46 | } 47 | } 48 | 49 | export function userIsLoading(state = false, action) { 50 | switch (action.type) { 51 | case 'USER_IS_LOADING': 52 | return action.isLoading; 53 | 54 | default: 55 | return state; 56 | } 57 | } 58 | 59 | export function user(state = {}, action) { 60 | switch (action.type) { 61 | case 'USER_FETCH_DATA_SUCCESS': 62 | return action.user; 63 | 64 | default: 65 | return state; 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /web/cfg/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let path = require('path'); 4 | let srcPath = path.join(__dirname, '/../src/'); 5 | 6 | let baseConfig = require('./base'); 7 | 8 | // Add needed plugins here 9 | let BowerWebpackPlugin = require('bower-webpack-plugin'); 10 | 11 | module.exports = { 12 | devtool: 'eval', 13 | module: { 14 | preLoaders: [ 15 | { 16 | test: /\.(js|jsx)$/, 17 | loader: 'isparta-instrumenter-loader', 18 | include: [ 19 | path.join(__dirname, '/../src') 20 | ] 21 | } 22 | ], 23 | loaders: [ 24 | { 25 | test: /\.(png|jpg|gif|woff|woff2|css|sass|scss|less|styl)$/, 26 | loader: 'null-loader' 27 | }, 28 | { 29 | test: /\.(js|jsx)$/, 30 | loader: 'babel-loader', 31 | include: [].concat( 32 | baseConfig.additionalPaths, 33 | [ 34 | path.join(__dirname, '/../src'), 35 | path.join(__dirname, '/../test') 36 | ] 37 | ) 38 | } 39 | ] 40 | }, 41 | resolve: { 42 | extensions: [ '', '.js', '.jsx' ], 43 | alias: { 44 | actions: srcPath + 'actions/', 45 | helpers: path.join(__dirname, '/../test/helpers'), 46 | components: srcPath + 'components/', 47 | sources: srcPath + 'sources/', 48 | stores: srcPath + 'stores/', 49 | styles: srcPath + 'styles/', 50 | config: srcPath + 'config/' + process.env.REACT_WEBPACK_ENV 51 | } 52 | }, 53 | plugins: [ 54 | new BowerWebpackPlugin({ 55 | searchResolveModulesDirectories: false 56 | }) 57 | ] 58 | }; 59 | -------------------------------------------------------------------------------- /web/src/components/BreadcrumbComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Breadcrumb } from 'semantic-ui-react' 5 | import PropTypes from 'prop-types'; 6 | 7 | require('styles//Breadcrumb.css'); 8 | 9 | class BreadcrumbComponent extends React.Component { 10 | static contextTypes = { 11 | router: PropTypes.object 12 | } 13 | 14 | constructor(props, context) { 15 | super(props, context); 16 | } 17 | 18 | navigate(route) { 19 | this.context.router.history.push(route); 20 | } 21 | 22 | handleClick = (e, { name }) => this.navigate(name) 23 | 24 | render() { 25 | 26 | return ( 27 |
28 | 29 | Home 30 | {this.props.links ? 31 | (this.props.links.map((link) => ( 32 | [ 33 | , 34 | 39 | {link.name} 40 | 41 | ] 42 | 43 | ))) : null 44 | } 45 | 46 |
47 | ); 48 | } 49 | } 50 | 51 | BreadcrumbComponent.displayName = 'BreadcrumbComponent'; 52 | 53 | // Uncomment properties you need 54 | // BreadcrumbComponent.propTypes = {}; 55 | // BreadcrumbComponent.defaultProps = {}; 56 | 57 | export default BreadcrumbComponent; 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | code/*.db 2 | assets.db 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | .static_storage/ 56 | .media/ 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /sensor/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | .static_storage/ 56 | .media/ 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /shared/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | .static_storage/ 56 | .media/ 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /web/src/reducers/networks.js: -------------------------------------------------------------------------------- 1 | export function networksHasErrored(state = false, action) { 2 | switch (action.type) { 3 | case 'NETWORKS_HAS_ERRORED': 4 | return action.hasErrored; 5 | 6 | default: 7 | return state; 8 | } 9 | } 10 | 11 | export function networksIsLoading(state = false, action) { 12 | switch (action.type) { 13 | case 'NETWORKS_IS_LOADING': 14 | return action.isLoading; 15 | 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export function networks(state = {}, action) { 22 | switch (action.type) { 23 | case 'NETWORKS_FETCH_DATA_SUCCESS': 24 | return action.networks; 25 | 26 | default: 27 | return state; 28 | } 29 | } 30 | 31 | 32 | 33 | 34 | // POST DATA // 35 | 36 | 37 | export function networksPostHasErrored(state = false, action) { 38 | switch (action.type) { 39 | case 'NETWORKS_POST_HAS_ERRORED': 40 | return action.hasErrored; 41 | 42 | default: 43 | return state; 44 | } 45 | } 46 | 47 | export function networksPostIsLoading(state = false, action) { 48 | switch (action.type) { 49 | case 'NETWORKS_POST_IS_LOADING': 50 | return action.isLoading; 51 | 52 | default: 53 | return state; 54 | } 55 | } 56 | 57 | export function networksPostDataSuccess(state = false, action) { 58 | switch (action.type) { 59 | case 'NETWORKS_POST_DATA_SUCCESS': 60 | return action.isSuccess; 61 | 62 | default: 63 | return state; 64 | } 65 | } 66 | 67 | 68 | // CHECK NETWORKS CHECKBOX // 69 | 70 | export function addNetworkToList(state = [], action) { 71 | switch (action.type) { 72 | case 'ADD_NETWORK_TO_LIST': 73 | return action.networkId; 74 | 75 | default: 76 | return state; 77 | } 78 | } -------------------------------------------------------------------------------- /web/cfg/defaults.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function that returns default values. 3 | * Used because Object.assign does a shallow instead of a deep copy. 4 | * Using [].push will add to the base array, so a require will alter 5 | * the base array output. 6 | */ 7 | 'use strict'; 8 | 9 | const path = require('path'); 10 | const srcPath = path.join(__dirname, '/../src'); 11 | const dfltPort = 8000; 12 | 13 | /** 14 | * Get the default modules object for webpack 15 | * @return {Object} 16 | */ 17 | function getDefaultModules() { 18 | return { 19 | preLoaders: [ 20 | { 21 | test: /\.(js|jsx)$/, 22 | include: srcPath, 23 | loader: 'eslint-loader' 24 | } 25 | ], 26 | loaders: [ 27 | { 28 | test: /\.css$/, 29 | loader: 'style-loader!css-loader' 30 | }, 31 | { 32 | test: /\.sass/, 33 | loader: 'style-loader!css-loader!sass-loader?outputStyle=expanded&indentedSyntax' 34 | }, 35 | { 36 | test: /\.scss/, 37 | loader: 'style-loader!css-loader!sass-loader?outputStyle=expanded' 38 | }, 39 | { 40 | test: /\.less/, 41 | loader: 'style-loader!css-loader!less-loader' 42 | }, 43 | { 44 | test: /\.styl/, 45 | loader: 'style-loader!css-loader!stylus-loader' 46 | }, 47 | { 48 | test: /\.(mp4|ogg|svg)$/, 49 | loader: 'file-loader' 50 | }, 51 | { 52 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 53 | loader: 'url-loader?limit=10000&mimetype=application/fontwoff' 54 | }, 55 | { 56 | test: /\.jpe?g$|\.gif$|\.png$|\.ttf$|\.eot$|\.svg$/, 57 | loader: 'file-loader?name=[name].[ext]?[hash]' 58 | }, 59 | 60 | ] 61 | }; 62 | } 63 | 64 | module.exports = { 65 | srcPath: srcPath, 66 | publicPath: '/assets/', 67 | port: dfltPort, 68 | getDefaultModules: getDefaultModules 69 | }; 70 | -------------------------------------------------------------------------------- /tools/insert_fake_networks.py: -------------------------------------------------------------------------------- 1 | from shared import api_wrapper as apiwr 2 | from random import randint 3 | from datetime import datetime 4 | 5 | api_url = 'http://localhost:8080' 6 | login = 'admin' 7 | password = 'password' 8 | 9 | api = apiwr.CoreApi( 10 | url=api_url, 11 | login=login, 12 | password=password 13 | ) 14 | 15 | fake_nets = [] 16 | for i in range(0,1): 17 | fake_num = randint(0,250) 18 | fake_net = '192.168.{}.0/24'.format(fake_num) 19 | if not fake_net in fake_nets: 20 | fake_nets.append(fake_net) 21 | api.update_network(name='TEST_' + str(fake_num), ip_addr=fake_net) 22 | print(fake_net) 23 | 24 | 25 | 26 | for net in api.get_networks(): 27 | host_count = randint(0,250) 28 | hosts = [] 29 | cur_host = { 30 | 'ip_addr': ".".join(net['ip_addr'].split('/')[0].split('.')[:-1]) +'.' + str(254), 31 | 'dns_names': 'host_test_{}'.format(i), 32 | 'network_id': net['id'], 33 | 'mac_addr': '00:50:F3:{}{}:{}{}'.format(randint(0,9), randint(0,9), randint(0,9), randint(0,9)), 34 | 'mac_vendor': 'VMWare', 35 | 'last_seen': datetime.now().isoformat(), 36 | 'services': [] 37 | } 38 | hosts.append(cur_host) 39 | for i in range(1, host_count): 40 | try: 41 | cur_host = { 42 | 'ip_addr': ".".join(net['ip_addr'].split('/')[0].split('.')[:-1]) +'.' + str(i), 43 | 'dns_names': 'host_test_{}'.format(i), 44 | 'network_id': net['id'], 45 | 'mac_addr': '00:50:F3:{}{}:{}{}'.format(randint(0,9), randint(0,9), randint(0,9), randint(0,9)), 46 | 'mac_vendor': 'VMWare', 47 | 'last_seen': datetime.now().isoformat(), 48 | 'services': [] 49 | } 50 | hosts.append(cur_host) 51 | api.add_host(**cur_host) 52 | print(cur_host) 53 | except: 54 | raise 55 | -------------------------------------------------------------------------------- /web/src/containers/SearchContainer.js: -------------------------------------------------------------------------------- 1 | require('normalize.css/normalize.css'); 2 | 3 | 4 | import React from 'react'; 5 | import HeaderComponent from '../components/HeaderComponent' 6 | import BreadcrumbComponent from '../components/BreadcrumbComponent' 7 | import SearchComponent from '../components/SearchComponent' 8 | 9 | // Redux 10 | import PropTypes from 'prop-types'; 11 | import { connect } from 'react-redux'; 12 | import { searchFetchData } from '../actions/search'; 13 | 14 | 15 | 16 | class SearchContainer extends React.Component { 17 | 18 | render() { 19 | const links = [ 20 | { 21 | 'route': '/search/', 22 | 'name': 'Search', 23 | 'active': true 24 | } 25 | ]; 26 | return ( 27 |
28 | 29 | 34 | 35 | 36 |
37 | 38 | ); 39 | } 40 | } 41 | 42 | 43 | SearchContainer.propTypes = { 44 | fetchData: PropTypes.func.isRequired, 45 | search: PropTypes.object.isRequired, 46 | hasErrored: PropTypes.bool.isRequired, 47 | isLoading: PropTypes.bool.isRequired, 48 | searchterm: PropTypes.string.isRequired 49 | }; 50 | 51 | const mapStateToProps = (state) => { 52 | return { 53 | search: state.search, 54 | hasErrored: state.searchHasErrored, 55 | isLoading: state.searchIsLoading, 56 | searchterm: state.searchterm 57 | }; 58 | }; 59 | 60 | const mapDispatchToProps = (dispatch) => { 61 | return { 62 | fetchData: (url) => dispatch(searchFetchData(url)) 63 | }; 64 | }; 65 | 66 | SearchContainer.displayName = 'SearchContainer'; 67 | 68 | export default connect(mapStateToProps, mapDispatchToProps)(SearchContainer); 69 | -------------------------------------------------------------------------------- /web/src/components/settings/PluginlistComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Table, Icon, Button } from 'semantic-ui-react' 5 | 6 | require('styles/settings/Pluginlist.css'); 7 | 8 | class PluginlistComponent extends React.Component { 9 | 10 | 11 | render() { 12 | if (this.props.pluginsisLoading) { 13 | 14 | const content =

Loading...

; 15 | return content; 16 | } 17 | 18 | return ( 19 |
20 | 21 | 22 | 23 | Name 24 | Type 25 | Version 26 | Actions 27 | 28 | 29 | 30 | 31 | {this.props.plugins.results.map((plugin) => ( 32 | 33 | 34 | {plugin.name} 35 | 36 | 37 | Scanner 38 | 39 | 40 | {plugin.version} 41 | 42 | 43 | 44 | 45 | 46 | ))} 47 | 48 |
49 |
50 | ); 51 | } 52 | } 53 | 54 | PluginlistComponent.displayName = 'SettingsPluginlistComponent'; 55 | 56 | // Uncomment properties you need 57 | // PluginlistComponent.propTypes = {}; 58 | // PluginlistComponent.defaultProps = {}; 59 | 60 | export default PluginlistComponent; 61 | -------------------------------------------------------------------------------- /web/src/actions/hosts.js: -------------------------------------------------------------------------------- 1 | import { Api } from '../lib/api.js' 2 | 3 | export function hostsHasErrored(bool) { 4 | return { 5 | type: 'HOSTS_HAS_ERRORED', 6 | hasErrored: bool 7 | }; 8 | } 9 | 10 | export function hostsIsLoading(bool) { 11 | return { 12 | type: 'HOSTS_IS_LOADING', 13 | isLoading: bool 14 | }; 15 | } 16 | 17 | export function hostsFetchDataSuccess(hosts) { 18 | return { 19 | type: 'HOSTS_FETCH_DATA_SUCCESS', 20 | hosts 21 | }; 22 | } 23 | 24 | export function hostHasErrored(bool) { 25 | return { 26 | type: 'HOST_HAS_ERRORED', 27 | hasErrored: bool 28 | }; 29 | } 30 | 31 | export function hostIsLoading(bool) { 32 | return { 33 | type: 'HOST_IS_LOADING', 34 | isLoading: bool 35 | }; 36 | } 37 | 38 | export function hostFetchDataSuccess(host) { 39 | return { 40 | type: 'HOST_FETCH_DATA_SUCCESS', 41 | host 42 | }; 43 | } 44 | 45 | 46 | export function hostFetchData(url) { 47 | return (dispatch) => { 48 | dispatch(hostIsLoading(true)); 49 | 50 | Api.get(url) 51 | .then((response) => { 52 | dispatch(hostIsLoading(false)); 53 | return response; 54 | }) 55 | .then((response) => response.json()) 56 | .then((host) => dispatch(hostFetchDataSuccess(host))) 57 | .catch(() => dispatch(hostHasErrored(true))); 58 | 59 | }; 60 | } 61 | 62 | export function hostsFetchData(url) { 63 | return (dispatch) => { 64 | dispatch(hostsIsLoading(true)); 65 | 66 | Api.get(url) 67 | .then((response) => { 68 | 69 | dispatch(hostsIsLoading(false)); 70 | 71 | return response; 72 | }) 73 | .then((response) => response.json()) 74 | .then((hosts) => dispatch(hostsFetchDataSuccess(hosts))) 75 | .catch(() => dispatch(hostsHasErrored(true))); 76 | 77 | }; 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /web/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | 2 | import { combineReducers } from 'redux'; 3 | 4 | import { networks, networksHasErrored, networksIsLoading } from './networks'; 5 | import { networksPostDataSuccess, networksPostIsLoading, networksPostHasErrored } from './networks'; 6 | import { addNetworkToList } from './networks' 7 | import { hosts, hostsHasErrored, hostsIsLoading, host, hostHasErrored, hostIsLoading } from './hosts'; 8 | import { metrics, metricsHasErrored, metricsIsLoading } from './metrics'; 9 | import { graph, graphHasErrored, graphIsLoading } from './graph'; 10 | import { sensors, sensorsHasErrored, sensorsIsLoading } from './sensors'; 11 | import { plugins, pluginsHasErrored, pluginsIsLoading } from './plugins'; 12 | import { users, usersHasErrored, usersIsLoading } from './users'; 13 | import { search, searchHasErrored, searchIsLoading, searchterm } from './search'; 14 | 15 | import { sessionReducer, loginLoading, loginFailed, userHasErrored, userIsLoading, user } from './sessions'; 16 | 17 | import { routerReducer } from 'react-router-redux' 18 | import { reducer as notifications } from 'react-notification-system-redux'; 19 | 20 | export default combineReducers({ 21 | networks, 22 | networksHasErrored, 23 | networksIsLoading, 24 | networksPostDataSuccess, 25 | networksPostIsLoading, 26 | networksPostHasErrored, 27 | addNetworkToList, 28 | hosts, 29 | hostsHasErrored, 30 | hostsIsLoading, 31 | host, 32 | hostHasErrored, 33 | hostIsLoading, 34 | metrics, 35 | metricsHasErrored, 36 | metricsIsLoading, 37 | graph, 38 | graphHasErrored, 39 | graphIsLoading, 40 | sensors, 41 | sensorsHasErrored, 42 | sensorsIsLoading, 43 | plugins, 44 | pluginsHasErrored, 45 | pluginsIsLoading, 46 | users, 47 | usersHasErrored, 48 | usersIsLoading, 49 | search, 50 | searchHasErrored, 51 | searchIsLoading, 52 | searchterm, 53 | sessionReducer, 54 | loginLoading, 55 | loginFailed, 56 | userHasErrored, 57 | userIsLoading, 58 | user, 59 | router: routerReducer, 60 | notifications 61 | }); -------------------------------------------------------------------------------- /web/src/containers/HomeContainer.js: -------------------------------------------------------------------------------- 1 | require('normalize.css/normalize.css'); 2 | 3 | 4 | import React from 'react'; 5 | 6 | // Redux 7 | import PropTypes from 'prop-types'; 8 | import { connect } from 'react-redux'; 9 | 10 | import { graphFetchData } from '../actions/graph'; 11 | import { metricsFetchData } from '../actions/metrics'; 12 | 13 | import HomeComponent from '../components/HomeComponent' 14 | import BreadcrumbComponent from '../components/BreadcrumbComponent' 15 | import HeaderComponent from '../components/HeaderComponent' 16 | 17 | 18 | class HomeContainer extends React.Component { 19 | constructor(props) { 20 | super(props); 21 | } 22 | 23 | componentDidMount() { 24 | if (localStorage.jwt) { 25 | this.props.metricsFetchData('/metrics/'); 26 | this.props.graphFetchData('/graphs/'); 27 | } 28 | } 29 | 30 | 31 | render() { 32 | const links = []; 33 | return ( 34 |
35 | 36 | 37 | 42 | 43 | 44 |
45 | 46 | 47 | ); 48 | } 49 | } 50 | 51 | 52 | HomeContainer.propTypes = { 53 | metricsFetchData: PropTypes.func.isRequired, 54 | metrics: PropTypes.object.isRequired, 55 | metricshasErrored: PropTypes.bool.isRequired, 56 | metricsisLoading: PropTypes.bool.isRequired, 57 | graphFetchData: PropTypes.func.isRequired, 58 | graph: PropTypes.object.isRequired, 59 | graphhasErrored: PropTypes.bool.isRequired, 60 | graphisLoading: PropTypes.bool.isRequired 61 | }; 62 | 63 | const mapStateToProps = (state) => { 64 | return { 65 | metrics: state.metrics, 66 | metricshasErrored: state.metricsHasErrored, 67 | metricsisLoading: state.metricsIsLoading, 68 | graph: state.graph, 69 | graphhasErrored: state.graphHasErrored, 70 | graphisLoading: state.graphIsLoading 71 | }; 72 | }; 73 | 74 | const mapDispatchToProps = (dispatch) => { 75 | return { 76 | metricsFetchData: (url) => dispatch(metricsFetchData(url)), 77 | graphFetchData: (url) => dispatch(graphFetchData(url)) 78 | }; 79 | }; 80 | 81 | HomeContainer.displayName = 'HomeContainer'; 82 | 83 | export default connect(mapStateToProps, mapDispatchToProps)(HomeContainer); 84 | -------------------------------------------------------------------------------- /shared/iftools.py: -------------------------------------------------------------------------------- 1 | from attrdict import AttrDict 2 | import netifaces as ni 3 | from ipaddress import ip_address 4 | import re 5 | 6 | 7 | def is_mac(mac): 8 | result = False 9 | 10 | result = bool( 11 | re.match('^' + '[\:\-]'.join(['([0-9a-f]{2})']*6) + '$', mac.lower())) 12 | 13 | return result 14 | 15 | 16 | def is_ip(ip): 17 | result = False 18 | try: 19 | result = ip_address(ip) 20 | if result: 21 | result = True 22 | except: 23 | pass 24 | return result 25 | 26 | 27 | def sensor_ifaces(): 28 | interfaces = [] 29 | 30 | sensor_interfaces = ni.interfaces() 31 | 32 | for iface in sensor_interfaces: 33 | if not 'lo' in iface: 34 | interface = { 35 | 'name': iface, 36 | } 37 | 38 | ifaddrs = ni.ifaddresses(iface).items() 39 | 40 | for info in ifaddrs: 41 | 42 | addr = info[1][0]['addr'] 43 | 44 | 45 | if is_mac(addr): 46 | interface['mac_addr'] = addr 47 | 48 | if is_ip(addr): 49 | interface['ip_addr'] = addr 50 | 51 | mask_infos = info[1][0] 52 | 53 | if 'netmask' in mask_infos: 54 | netmask = mask_infos['netmask'] 55 | if is_ip(netmask): 56 | interface['netmask'] = netmask 57 | 58 | 59 | 60 | if 'ip_addr' in interface.keys(): 61 | interface = AttrDict(interface) 62 | if not interface in interfaces: 63 | interfaces.append(interface) 64 | 65 | return interfaces 66 | 67 | 68 | def which_iface(ip): 69 | result = None 70 | # Remove netmask 71 | if "/" in ip: 72 | ip = ip.split('/')[0] 73 | 74 | # Remove last digit 75 | ip = ip.split('.')[:-1] 76 | ip = ".".join(ip) 77 | 78 | for iface in sensor_ifaces(): 79 | try: 80 | if hasattr(iface, 'ip_addr'): 81 | sensorip = iface.ip_addr 82 | if sensorip.startswith(ip): 83 | result = iface.name 84 | 85 | except ValueError: 86 | pass 87 | 88 | return result 89 | 90 | if __name__ == "__main__": 91 | print("INTERFACES", sensor_ifaces()) 92 | print("IS IP: ", is_ip("192.168.56.22")) 93 | print("WHICH IFACE:", which_iface("192.168.56.22")) 94 | print("IS MAC:", is_mac("0a:00:27:00:00:00")) 95 | -------------------------------------------------------------------------------- /web/src/components/settings/SensorListComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Card, Button, Table } from 'semantic-ui-react'; 5 | 6 | require('styles/settings/SensorList.css'); 7 | 8 | class SensorListComponent extends React.Component { 9 | render() { 10 | if (this.props.isLoading) { 11 | return null; 12 | } 13 | return ( 14 |
15 | 16 | {this.props.sensors.results.map((sensor) => ( 17 | 18 | 19 | 20 | 21 | {sensor.name} 22 | 23 | 24 | {sensor.dns_names} 25 | 26 | 27 | {sensor.version} 28 | {sensor.ip_addr} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | iFace 37 | IP Address 38 | 39 | 40 | 41 | 42 | {sensor.interfaces.map((iface) => ( 43 | 44 | {iface.name} 45 | {iface.ip_addr} 46 | 47 | ))} 48 | 49 | 50 |
51 | 52 |
53 | 54 |
55 | 56 |
57 |
58 |
59 | ))} 60 |
61 |
62 | ); 63 | } 64 | } 65 | 66 | SensorListComponent.displayName = 'SensorListComponent'; 67 | 68 | // Uncomment properties you need 69 | // SensorListComponent.propTypes = {}; 70 | // SensorListComponent.defaultProps = {}; 71 | 72 | export default SensorListComponent; 73 | -------------------------------------------------------------------------------- /web/src/containers/NetworkListContainer.js: -------------------------------------------------------------------------------- 1 | require('normalize.css/normalize.css'); 2 | 3 | 4 | import React from 'react'; 5 | import NetworkListComponent from '../components/NetworkListComponent' 6 | import HeaderComponent from '../components/HeaderComponent' 7 | import BreadcrumbComponent from '../components/BreadcrumbComponent' 8 | import SidebarComponent from '../components/SidebarComponent' 9 | import { Grid } from 'semantic-ui-react' 10 | // Redux 11 | import PropTypes from 'prop-types'; 12 | import { connect } from 'react-redux'; 13 | import { networksFetchData, networksDeleteData } from '../actions/networks'; 14 | 15 | class NetworkListContainer extends React.Component { 16 | 17 | componentDidMount() { 18 | this.props.fetchData('/networks/'); 19 | } 20 | 21 | render() { 22 | const links = [ 23 | { 24 | 'route': '/networks/', 25 | 'name': 'Networks', 26 | 'active': true 27 | } 28 | ]; 29 | return ( 30 |
31 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 | ); 51 | } 52 | } 53 | 54 | 55 | NetworkListContainer.propTypes = { 56 | fetchData: PropTypes.func.isRequired, 57 | networks: PropTypes.object.isRequired, 58 | hasErrored: PropTypes.bool.isRequired, 59 | isLoading: PropTypes.bool.isRequired, 60 | networksDeleteData: PropTypes.func.isRequired 61 | }; 62 | 63 | const mapStateToProps = (state) => { 64 | return { 65 | networks: state.networks, 66 | hasErrored: state.networksHasErrored, 67 | isLoading: state.networksIsLoading 68 | }; 69 | }; 70 | 71 | const mapDispatchToProps = (dispatch) => { 72 | return { 73 | fetchData: (url) => dispatch(networksFetchData(url)), 74 | networksDeleteData: (url, networks) => dispatch(networksDeleteData(url, networks)) 75 | }; 76 | }; 77 | 78 | NetworkListContainer.displayName = 'NetworkListContainer'; 79 | 80 | export default connect(mapStateToProps, mapDispatchToProps)(NetworkListContainer); 81 | -------------------------------------------------------------------------------- /web/src/containers/HostDetailContainer.js: -------------------------------------------------------------------------------- 1 | require('normalize.css/normalize.css'); 2 | 3 | 4 | import React from 'react'; 5 | import HostDetailComponent from '../components/HostDetailComponent' 6 | import BreadcrumbComponent from '../components/BreadcrumbComponent' 7 | import HeaderComponent from '../components/HeaderComponent' 8 | 9 | // Redux 10 | import PropTypes from 'prop-types'; 11 | import { connect } from 'react-redux'; 12 | import { hostFetchData } from '../actions/hosts'; 13 | 14 | class HostDetailContainer extends React.Component { 15 | constructor(props) { 16 | super(props); 17 | } 18 | 19 | componentWillMount() { 20 | this.props.fetchData('/hosts/' + this.props.match.params.id + '/'); 21 | } 22 | 23 | getbreadcrumblinks(props) { 24 | var links = [ 25 | { 26 | 'route': '/networks/', 27 | 'name': 'Networks', 28 | 'active': false 29 | }, 30 | { 31 | 'route': '/networks/' + props.host.host.network.id, 32 | 'name': props.host.host.network.name, 33 | 'active': false 34 | }, 35 | { 36 | 'route': '/hosts/' + props.host.host.id, 37 | 'name': props.host.host.ip_addr, 38 | 'active': true 39 | } 40 | ]; 41 | return links; 42 | } 43 | 44 | render() { 45 | 46 | let content; 47 | 48 | if (this.props.hasErrored) { 49 | content =

Sorry! There was an error

; 50 | return content; 51 | } 52 | 53 | return ( 54 |
55 | 57 | 62 | 63 |
64 | ); 65 | 66 | } 67 | } 68 | 69 | 70 | HostDetailContainer.propTypes = { 71 | fetchData: PropTypes.func.isRequired, 72 | host: PropTypes.object.isRequired, 73 | hasErrored: PropTypes.bool.isRequired, 74 | isLoading: PropTypes.bool.isRequired 75 | 76 | }; 77 | 78 | const mapStateToProps = (state) => { 79 | return { 80 | host: state.host, 81 | hasErrored: state.hostHasErrored, 82 | isLoading: state.hostIsLoading 83 | 84 | }; 85 | }; 86 | 87 | const mapDispatchToProps = (dispatch) => { 88 | return { 89 | fetchData: (url) => dispatch(hostFetchData(url)) 90 | }; 91 | }; 92 | 93 | HostDetailContainer.displayName = 'HostDetailContainer'; 94 | 95 | export default connect(mapStateToProps, mapDispatchToProps)(HostDetailContainer); 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NMT 2 | 3 | A network mapper / scanner for improving the pentesters/admin/engineers knowledge of local networks (maybe public soon). 4 | 5 | 6 | 7 | ![pipeline status](https://travis-ci.org/gbnk0/nmt.svg?branch=develop) 8 | ![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fgbnk0%2Fnmt.svg?type=shield) 9 | ![coverage](https://coveralls.io/repos/github/gbnk0/nmt/badge.svg?branch=develop) 10 | ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg) 11 | ![python_version](https://img.shields.io/badge/python-3.5%2C3.6-blue.svg) 12 | 13 | 14 | 15 | 16 | ## DOCUMENTATION 17 | [Jump to the documentation](https://github.com/gbnk0/nmt/wiki) 18 | 19 | 20 | ## QUICK START 21 | 22 | ```bash 23 | cd docker/ 24 | docker-compose -f docker-compose-all.yml up --build 25 | ``` 26 | 27 | Go to localhost:8000 in your browser and login with the default credentials 28 | 29 | You can now add the first network to scan and launch the sensor: 30 | 31 | 32 | ```bash 33 | sudo pip3 install -r requirements.txt 34 | cd sensor/ 35 | python3 sensor.py 36 | ``` 37 | The scan process will take a time, depending on your sensor.conf config file. 38 | 39 | ##### DEFAULT CREDENTIALS 40 | `admin / password` 41 | 42 | 43 | ## INSTALLATION (DEV) 44 | ```bash 45 | git clone git@gitlab.com:gbnk0/nmt.git 46 | ``` 47 | ### DEVELOPEMENT ENV REQUIREMENTS 48 | - Minimum python version: 3.5 49 | - Install docker: 50 | ```bash 51 | curl https://get.docker.com/|sudo sh 52 | ``` 53 | 54 | - Install docker-compose: 55 | ```bash 56 | pip3 install docker-compose 57 | ``` 58 | 59 | - Install NodeJS 8 LTS: 60 | ```bash 61 | curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - 62 | sudo apt-get install -y nodejs 63 | ``` 64 | 65 | - Install requirements: 66 | ```bash 67 | pip3 install -r requirements.txt 68 | ``` 69 | 70 | ### CORE API 71 | #### LAUNCH THE API 72 | - Move into the core folder: 73 | 74 | ```bash 75 | cd core/ 76 | ``` 77 | 78 | - The api needs a RethinkDB instance for storing hosts, networks data, we'll use docker-compose for bringing it up: 79 | 80 | ```bash 81 | docker-compose -f docker/docker-compose.yml up #( add -d for background ) 82 | 83 | ``` 84 | 85 | - Now you can install dependencies, and run the api with sanic-admin: 86 | 87 | ```bash 88 | pip3 install sanic-admin 89 | sanic-admin api.py 90 | ``` 91 | 92 | 93 | #### LAUNCH THE SENSOR 94 | ```bash 95 | cd sensor/ 96 | python3 sensor.py 97 | ``` 98 | 99 | 100 | ### WEB UI 101 | #### LAUNCH THE WEB UI (DEV MODE) 102 | ( Soon with docker-compose UP) 103 | 104 | ```bash 105 | cd web/ 106 | npm install # (wait...) 107 | npm start 108 | ``` 109 | 110 | 111 | ## License 112 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fgbnk0%2Fnmt.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fgbnk0%2Fnmt?ref=badge_large) 113 | -------------------------------------------------------------------------------- /web/src/containers/NetworkDetailContainer.js: -------------------------------------------------------------------------------- 1 | require('normalize.css/normalize.css'); 2 | 3 | 4 | import React from 'react'; 5 | import { Grid } from 'semantic-ui-react' 6 | import NetworkDetailComponent from '../components/NetworkDetailComponent' 7 | import BreadcrumbComponent from '../components/BreadcrumbComponent' 8 | import HeaderComponent from '../components/HeaderComponent' 9 | 10 | // Redux 11 | import PropTypes from 'prop-types'; 12 | import { connect } from 'react-redux'; 13 | import { hostsFetchData } from '../actions/hosts'; 14 | // import { graphFetchData } from '../actions/graph'; 15 | 16 | 17 | class NetworkDetailsContainer extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | } 21 | 22 | componentDidMount() { 23 | const networkId = this.props.match.params.id 24 | this.props.fetchData('/networks/' + networkId + '/'); 25 | // this.props.graphFetchData('/graphs/' + networkId + '/'); 26 | } 27 | 28 | render() { 29 | const links = [ 30 | { 31 | 'route': '/networks/', 32 | 'name': 'Networks', 33 | 'active': false 34 | }, 35 | { 36 | 'route': '/networks/' + this.props.hosts.network.id, 37 | 'name': this.props.hosts.network.name, 38 | 'active': true 39 | } 40 | ]; 41 | return ( 42 |
43 | 45 | 50 | 51 | 52 | 53 |
54 | 55 | 56 | 57 | ); 58 | } 59 | } 60 | 61 | 62 | NetworkDetailsContainer.propTypes = { 63 | fetchData: PropTypes.func.isRequired, 64 | hosts: PropTypes.object.isRequired, 65 | hasErrored: PropTypes.bool.isRequired, 66 | isLoading: PropTypes.bool.isRequired, 67 | // graphFetchData: PropTypes.func.isRequired, 68 | // graph: PropTypes.object.isRequired, 69 | // graphhasErrored: PropTypes.bool.isRequired, 70 | // graphisLoading: PropTypes.bool.isRequired 71 | 72 | }; 73 | 74 | const mapStateToProps = (state) => { 75 | return { 76 | hosts: state.hosts, 77 | hasErrored: state.hostsHasErrored, 78 | isLoading: state.hostsIsLoading, 79 | // graph: state.graph, 80 | // graphhasErrored: state.graphHasErrored, 81 | // graphisLoading: state.graphIsLoading 82 | 83 | }; 84 | }; 85 | 86 | const mapDispatchToProps = (dispatch) => { 87 | return { 88 | fetchData: (url) => dispatch(hostsFetchData(url)) 89 | // graphFetchData: (url) => dispatch(graphFetchData(url)) 90 | }; 91 | }; 92 | 93 | NetworkDetailsContainer.displayName = 'NetworkDetailsContainer'; 94 | 95 | export default connect(mapStateToProps, mapDispatchToProps)(NetworkDetailsContainer); 96 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "version": "0.0.1", 4 | "description": "YOUR DESCRIPTION - Generated by generator-react-webpack", 5 | "main": "", 6 | "scripts": { 7 | "clean": "rimraf dist/*", 8 | "copy": "copyfiles -f ./src/index.html ./src/favicon.ico ./dist", 9 | "dist": "npm run copy & webpack --env=dist", 10 | "lint": "eslint ./src", 11 | "posttest": "npm run lint", 12 | "release:major": "npm version major && npm publish && git push --follow-tags", 13 | "release:minor": "npm version minor && npm publish && git push --follow-tags", 14 | "release:patch": "npm version patch && npm publish && git push --follow-tags", 15 | "serve": "node server.js --env=dev", 16 | "serve:dist": "node server.js --env=dist", 17 | "start": "node server.js --env=dev", 18 | "test": "karma start", 19 | "test:watch": "karma start --autoWatch=true --singleRun=false" 20 | }, 21 | "repository": "", 22 | "keywords": [], 23 | "author": "Your name here", 24 | "devDependencies": { 25 | "babel-core": "^6.0.0", 26 | "babel-eslint": "^6.0.0", 27 | "babel-loader": "^6.0.0", 28 | "babel-polyfill": "^6.3.14", 29 | "babel-preset-es2015": "^6.0.15", 30 | "babel-preset-react": "^6.0.15", 31 | "babel-preset-stage-0": "^6.5.0", 32 | "bower-webpack-plugin": "^0.1.9", 33 | "chai": "^3.2.0", 34 | "copyfiles": "^1.0.0", 35 | "css-loader": "^0.23.0", 36 | "eslint": "^3.0.0", 37 | "eslint-loader": "^1.0.0", 38 | "eslint-plugin-react": "^6.0.0", 39 | "file-loader": "^0.9.0", 40 | "glob": "^7.0.0", 41 | "isparta-instrumenter-loader": "^1.0.0", 42 | "karma": "^1.0.0", 43 | "karma-chai": "^0.1.0", 44 | "karma-coverage": "^1.0.0", 45 | "karma-mocha": "^1.0.0", 46 | "karma-mocha-reporter": "^2.0.0", 47 | "karma-phantomjs-launcher": "^1.0.0", 48 | "karma-sourcemap-loader": "^0.3.5", 49 | "karma-webpack": "^1.7.0", 50 | "minimist": "^1.2.0", 51 | "mocha": "^3.0.0", 52 | "null-loader": "^0.1.1", 53 | "open": "0.0.5", 54 | "react-addons-test-utils": "^15.0.0", 55 | "react-hot-loader": "^1.2.9", 56 | "rimraf": "^2.4.3", 57 | "style-loader": "^0.13.0", 58 | "url-loader": "^0.5.9", 59 | "webpack": "^1.12.0", 60 | "webpack-dev-server": "^1.12.0" 61 | }, 62 | "dependencies": { 63 | "components": "^0.1.0", 64 | "config": "^1.29.0", 65 | "core-js": "^2.0.0", 66 | "helpers": "^0.0.6", 67 | "history": "^4.7.2", 68 | "ip-address": "^5.8.8", 69 | "moment": "^2.19.1", 70 | "normalize.css": "^4.0.0", 71 | "prop-types": "^15.6.0", 72 | "react": "^15.0.0", 73 | "react-dom": "^15.0.0", 74 | "react-graph-vis": "^1.0.2", 75 | "react-notification-system": "^0.2.16", 76 | "react-notification-system-redux": "^1.2.0", 77 | "react-redux": "^5.0.6", 78 | "react-router": "^4.2.0", 79 | "react-router-dom": "^4.2.2", 80 | "react-router-redux": "^5.0.0-alpha.8", 81 | "redux": "^3.7.2", 82 | "redux-thunk": "^2.2.0", 83 | "semantic-ui-css": "^2.2.12", 84 | "semantic-ui-react": "^0.75.1", 85 | "styles": "^0.2.1" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /web/src/components/NetworkDetailComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Icon, Table } from 'semantic-ui-react'; 5 | 6 | import { formatDate, renderOs } from '../lib/tools'; 7 | import PropTypes from 'prop-types'; 8 | 9 | require('styles//NetworkDetail.css'); 10 | 11 | class NetworkDetailComponent extends React.Component { 12 | static contextTypes = { 13 | router: PropTypes.object 14 | } 15 | 16 | constructor(props, context) { 17 | super(props, context); 18 | } 19 | navigate(route) { 20 | this.props.history.push('/hosts/' + route); 21 | } 22 | 23 | handleClick = (e, { key }) => this.navigate(key) 24 | 25 | render() { 26 | let content; 27 | 28 | if (this.props.hasErrored) { 29 | content =

API Unavailable

; 30 | return content; 31 | } 32 | return ( 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | Hostname 41 | IP Address 42 | MAC 43 | Vendor 44 | OS 45 | Open ports 46 | Last seen 47 | 48 | 49 | 50 | 51 | {this.props.hosts.results.map((host) => ( 52 | 53 | 56 | 57 | 58 | 59 | {host.dns_names} 60 | {host.ip_addr} 61 | {host.mac_addr} 62 | {host.mac_vendor} 63 | {renderOs(host)} 64 | {host.open_ports} 65 | {formatDate(host.last_seen)} 66 | 67 | 68 | ))} 69 | 70 | 71 | 72 | 73 |
74 |
75 | ); 76 | 77 | } 78 | } 79 | 80 | 81 | 82 | NetworkDetailComponent.displayName = 'NetworkDetailComponent'; 83 | 84 | // Uncomment properties you need 85 | // NetworkDetailComponent.propTypes = {}; 86 | // NetworkDetailComponent.defaultProps = {}; 87 | 88 | export default NetworkDetailComponent; -------------------------------------------------------------------------------- /shared/api_wrapper.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from attrdict import AttrDict 4 | 5 | conn_headers = { 6 | 'Content-type': 'application/json' 7 | } 8 | 9 | class CoreApi: 10 | 11 | def __init__(self, url, **kwargs): 12 | self.url = url 13 | self.login = kwargs.get('login') 14 | self.password = kwargs.get('password') 15 | self.headers = conn_headers 16 | 17 | if self.login or self.password: 18 | self.headers['Authorization'] = self.get_auth_token() 19 | 20 | self.check = requests.get(url + "/check") 21 | 22 | """ AUTH PART """ 23 | 24 | def get_auth_token(self): 25 | auth_url = "{}/auth".format(self.url) 26 | 27 | creds = { 28 | 'email': self.login, 29 | 'password' : self.password 30 | } 31 | 32 | r = requests.post(auth_url, json=creds) 33 | 34 | token = r.json()['access_token'] 35 | auth_string = 'Bearer {}'.format(token) 36 | 37 | return auth_string 38 | 39 | """ SENSORS PART """ 40 | 41 | def update_sensor(self, **kwargs): 42 | 43 | url = "{}/sensors/".format(self.url) 44 | 45 | data = json.dumps(kwargs) 46 | 47 | r = requests.post(url, 48 | json=data, 49 | headers=self.headers 50 | ) 51 | if r.status_code == 201: 52 | return True 53 | else: 54 | return False 55 | 56 | """ NETWORKS PART """ 57 | 58 | def update_network(self, **kwargs): 59 | 60 | url = "{}/networks/".format(self.url) 61 | 62 | data = json.dumps(kwargs) 63 | 64 | r = requests.post(url, 65 | json=data, 66 | headers=self.headers 67 | ) 68 | if r.status_code == 201: 69 | return True 70 | else: 71 | return False 72 | 73 | def get_networks(self, **kwargs): 74 | url = "{}/networks/".format(self.url) 75 | 76 | filters = kwargs.get('filter', None) 77 | 78 | if filters: 79 | url += '?filter='+str(filters) 80 | 81 | r = requests.get(url, headers=self.headers) 82 | 83 | if r.status_code == 200: 84 | return AttrDict({'networks': json.loads(r.text)['results']}).networks 85 | else: 86 | return [] 87 | 88 | """ HOSTS PART """ 89 | # Update / Add host in a network 90 | 91 | def add_host(self, **kwargs): 92 | 93 | network_id = kwargs.get('network_id', None) 94 | url = "{}/networks/{}/".format(self.url, network_id) 95 | data = json.dumps(kwargs) 96 | r = requests.post(url, 97 | json=data, 98 | headers=self.headers 99 | ) 100 | if r.status_code == 201: 101 | return True 102 | else: 103 | return False 104 | 105 | def get_macvendor(self, mac_addr): 106 | url = "{}/macvendor/{}/".format(self.url, mac_addr) 107 | r = requests.get(url, headers=self.headers) 108 | 109 | if r.status_code == 200: 110 | return json.loads(r.text)['vendor'] 111 | else: 112 | return "" 113 | -------------------------------------------------------------------------------- /web/src/containers/MainContainer.js: -------------------------------------------------------------------------------- 1 | require('normalize.css/normalize.css'); 2 | require('styles/App.css'); 3 | // Redux 4 | import React from 'react'; 5 | 6 | import { Grid } from 'semantic-ui-react' 7 | 8 | import NavbarComponent from '../components/NavbarComponent.js' 9 | import {connect} from 'react-redux'; 10 | import Routes from '../routes'; 11 | import { withRouter } from 'react-router-dom' 12 | import { push } from 'react-router-redux' 13 | import PropTypes from 'prop-types'; 14 | import { userFetchData } from '../actions/sessions' 15 | 16 | import Notifications from 'react-notification-system-redux'; 17 | 18 | class MainContainer extends React.Component { 19 | constructor(props) { 20 | super(props); 21 | } 22 | 23 | componentWillMount() { 24 | // If not logged_in and trying to get another route than /login 25 | if (!this.props.logged_in && ( this.props.router.location.pathname != '/login/') && (!localStorage.jwt)) { 26 | this.props.pushToHistory('/login/'); 27 | } 28 | if (this.props.logged_in) { 29 | this.props.userFetchData('/auth/me'); 30 | } 31 | } 32 | 33 | componentDidMount() { 34 | 35 | } 36 | 37 | getContent(props) { 38 | var content = ; 39 | if (props.logged_in) { 40 | var content = ( 41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
53 | ); 54 | 55 | }; 56 | 57 | return content; 58 | } 59 | 60 | render() { 61 | const { notifications } = this.props; 62 | const style = { 63 | NotificationItem: { 64 | DefaultStyle: { 65 | marginTop: '50px' 66 | } 67 | 68 | } 69 | }; 70 | 71 | return ( 72 | 73 |
74 |
75 | 79 | {this.getContent(this.props)} 80 | 81 |
82 |
83 | ); 84 | 85 | } 86 | } 87 | 88 | 89 | MainContainer.propTypes = { 90 | pushToHistory: PropTypes.func.isRequired, 91 | userFetchData: PropTypes.func.isRequired, 92 | user: PropTypes.object.isRequired, 93 | userHasErrored: PropTypes.bool.isRequired, 94 | userIsLoading: PropTypes.bool.isRequired, 95 | notifications: PropTypes.array 96 | } 97 | 98 | function mapStateToProps(state) { 99 | return { 100 | logged_in: state.sessionReducer, 101 | router: state.router, 102 | user: state.user, 103 | userHasErrored: state.userHasErrored, 104 | userIsLoading: state.userIsLoading, 105 | notifications: state.notifications 106 | }; 107 | } 108 | 109 | 110 | const mapDispatchToProps = (dispatch) => { 111 | return { 112 | pushToHistory: (route) => dispatch(push(route)), 113 | userFetchData: (url) => dispatch(userFetchData(url)) 114 | }; 115 | }; 116 | 117 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MainContainer)); 118 | 119 | -------------------------------------------------------------------------------- /web/src/lib/api.js: -------------------------------------------------------------------------------- 1 | 2 | export function getApiUrl() { 3 | const ApiUrl = localStorage.getItem('ApiUrl'); 4 | 5 | if (ApiUrl !== null) { 6 | return ApiUrl 7 | } else { 8 | return 'localhost:8080' 9 | } 10 | 11 | } 12 | export var ApiUrl = 'http://' + getApiUrl() 13 | 14 | export const forbiddenStatuses = [401, 403, 500] 15 | 16 | 17 | export class sessionApi { 18 | 19 | static login(credentials) { 20 | const request = new Request(ApiUrl + '/auth', { 21 | method: 'POST', 22 | headers: new Headers({ 23 | 'Accept': 'application/json', 24 | 'Content-Type': 'application/json, text/plain' 25 | }), 26 | body: JSON.stringify(credentials) 27 | 28 | }); 29 | 30 | return fetch(request).then(response => { 31 | if (response.status == 401) { 32 | return {'status': 'failed'} 33 | } 34 | return response.json(); 35 | }).catch(error => { 36 | return error; 37 | }); 38 | } 39 | 40 | 41 | static verifyToken() { 42 | const request = new Request(ApiUrl + '/auth/verify', { 43 | method: 'GET', 44 | headers: new Headers({ 45 | 'Authorization': `Bearer ${localStorage.jwt}` 46 | }) 47 | }); 48 | 49 | return fetch(request).then(response => { 50 | if (response.status == 400) { 51 | localStorage.removeItem('jwt'); 52 | } 53 | return response.json(); 54 | 55 | }).catch(error => { 56 | localStorage.removeItem('jwt'); 57 | return error; 58 | }); 59 | } 60 | 61 | } 62 | 63 | 64 | export class Api { 65 | 66 | 67 | static get(path) { 68 | const request = new Request(ApiUrl + path, { 69 | method: 'GET', 70 | headers: new Headers({ 71 | 'Authorization': `Bearer ${localStorage.jwt}` 72 | }) 73 | }); 74 | 75 | return fetch(request).then(response => { 76 | if (!response.ok) { 77 | 78 | if (forbiddenStatuses.includes(response.status)) { 79 | 80 | sessionApi.verifyToken() 81 | } 82 | throw Error(response.statusText); 83 | } 84 | return response; 85 | }).catch(error => { 86 | return error; 87 | }); 88 | } 89 | 90 | static post(path, payload) { 91 | const request = new Request(ApiUrl + path, { 92 | method: 'POST', 93 | headers: new Headers({ 94 | 'Authorization': `Bearer ${localStorage.jwt}` 95 | }), 96 | body: JSON.stringify(payload) 97 | }); 98 | 99 | return fetch(request).then(response => { 100 | if (!response.ok) { 101 | if (forbiddenStatuses.includes(response.status)) { 102 | sessionApi.verifyToken() 103 | } 104 | throw Error(response.statusText); 105 | } 106 | return response; 107 | }).catch(error => { 108 | return error; 109 | }); 110 | } 111 | 112 | static delete(path, payload) { 113 | const request = new Request(ApiUrl + path, { 114 | method: 'DELETE', 115 | headers: new Headers({ 116 | 'Authorization': `Bearer ${localStorage.jwt}` 117 | }), 118 | body: JSON.stringify(payload) 119 | }); 120 | 121 | return fetch(request).then(response => { 122 | if (!response.ok) { 123 | if (response.status in forbiddenStatuses) { 124 | sessionApi.verifyToken() 125 | } 126 | throw Error(response.statusText); 127 | } 128 | return response; 129 | }).catch(error => { 130 | return error; 131 | }); 132 | } 133 | 134 | } 135 | 136 | -------------------------------------------------------------------------------- /web/src/components/HostDetailComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Grid, Card, Tab, Icon } from 'semantic-ui-react' 5 | import ServicesComponent from './hostdetail/ServicesComponent' 6 | import { formatDate, renderOs } from '../lib/tools'; 7 | 8 | require('styles//HostDetail.css'); 9 | 10 | class HostDetailComponent extends React.Component { 11 | renderpanes(props) { 12 | const panes = [ 13 | { 14 | menuItem: { key: 'services', icon: 'tasks', content: 'Services' }, 15 | render: () => 16 | }, 17 | { 18 | menuItem: { key: 'history', icon: 'browser', content: 'History' }, 19 | render: () => No history 20 | } 21 | ] 22 | return panes; 23 | } 24 | 25 | render() { 26 | 27 | if (this.props.isLoading) { 28 | const content =

Loading...

; 29 | return content; 30 | } 31 | return ( 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Network 41 | 42 | 43 | {this.props.host.host.ip_addr} 44 | 45 | 46 | {this.props.host.host.mac_addr} 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Last seen 61 | 62 | 63 | {formatDate(this.props.host.host.last_seen)} 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | DNS 73 | 74 | 75 | {this.props.host.host.dns_names} 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | Operating system 85 | 86 | 87 | {renderOs(this.props.host)} 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 |
105 | ); 106 | } 107 | } 108 | 109 | HostDetailComponent.displayName = 'HostDetailComponent'; 110 | 111 | // Uncomment properties you need 112 | // HostDetailComponent.propTypes = {}; 113 | // HostDetailComponent.defaultProps = {}; 114 | 115 | export default HostDetailComponent; 116 | -------------------------------------------------------------------------------- /web/src/containers/SettingsContainer.js: -------------------------------------------------------------------------------- 1 | require('normalize.css/normalize.css'); 2 | 3 | 4 | import React from 'react'; 5 | 6 | import UserlistComponent from '../components/settings/UserlistComponent' 7 | import SensorListComponent from '../components/settings/SensorListComponent' 8 | import PluginlistComponent from '../components/settings/PluginlistComponent' 9 | 10 | import HeaderComponent from '../components/HeaderComponent' 11 | import BreadcrumbComponent from '../components/BreadcrumbComponent' 12 | import { Tab } from 'semantic-ui-react' 13 | 14 | // Redux 15 | import PropTypes from 'prop-types'; 16 | import { connect } from 'react-redux'; 17 | import { usersFetchData } from '../actions/users'; 18 | import { sensorsFetchData } from '../actions/sensors'; 19 | import { pluginsFetchData } from '../actions/plugins'; 20 | 21 | 22 | const panes = (props) => [ 23 | { 24 | menuItem: { key: 'users', icon: 'users', content: 'Users' }, 25 | render: () => 26 | }, 27 | { 28 | menuItem: { key: 'sensors', icon: 'circle outline', content: 'Sensors' }, 29 | render: () => 30 | }, 31 | { 32 | menuItem: { key: 'plugins', icon: 'plug', content: 'Plugins' }, 33 | render: () => 34 | } 35 | ] 36 | 37 | class SettingsContainer extends React.Component { 38 | constructor(props) { 39 | super(props); 40 | } 41 | 42 | componentDidMount() { 43 | this.props.usersFetchData('/users/'); 44 | this.props.sensorsFetchData('/sensors/'); 45 | this.props.pluginsFetchData('/plugins/'); 46 | } 47 | 48 | render() { 49 | const links = [ 50 | { 51 | 'route': '/settings/', 52 | 'name': 'Settings', 53 | 'active': true 54 | } 55 | ]; 56 | return ( 57 |
58 | 59 | 64 | 65 |
66 | 67 | 68 | 69 | ); 70 | } 71 | } 72 | 73 | 74 | SettingsContainer.propTypes = { 75 | usersFetchData: PropTypes.func.isRequired, 76 | users: PropTypes.object.isRequired, 77 | usershasErrored: PropTypes.bool.isRequired, 78 | usersisLoading: PropTypes.bool.isRequired, 79 | sensorsFetchData: PropTypes.func.isRequired, 80 | sensors: PropTypes.object.isRequired, 81 | sensorshasErrored: PropTypes.bool.isRequired, 82 | sensorsisLoading: PropTypes.bool.isRequired, 83 | plugins: PropTypes.object.isRequired, 84 | pluginshasErrored: PropTypes.bool.isRequired, 85 | pluginsisLoading: PropTypes.bool.isRequired 86 | }; 87 | 88 | const mapStateToProps = (state) => { 89 | return { 90 | users: state.users, 91 | usershasErrored: state.usersHasErrored, 92 | usersisLoading: state.usersIsLoading, 93 | sensors: state.sensors, 94 | sensorshasErrored: state.sensorsHasErrored, 95 | sensorsisLoading: state.sensorsIsLoading, 96 | plugins: state.plugins, 97 | pluginshasErrored: state.pluginsHasErrored, 98 | pluginsisLoading: state.pluginsIsLoading 99 | }; 100 | }; 101 | 102 | const mapDispatchToProps = (dispatch) => { 103 | return { 104 | usersFetchData: (url) => dispatch(usersFetchData(url)), 105 | sensorsFetchData: (url) => dispatch(sensorsFetchData(url)), 106 | pluginsFetchData: (url) => dispatch(pluginsFetchData(url)) 107 | }; 108 | }; 109 | 110 | SettingsContainer.displayName = 'SettingsContainer'; 111 | 112 | export default connect(mapStateToProps, mapDispatchToProps)(SettingsContainer); 113 | -------------------------------------------------------------------------------- /web/src/actions/networks.js: -------------------------------------------------------------------------------- 1 | import { Api } from '../lib/api.js' 2 | import Notifications, { success, error, info } from 'react-notification-system-redux'; 3 | 4 | function deleteNetworkNotification(netCount) { 5 | const SuccessDeleteNotification = { 6 | title: 'Network deletion', 7 | message: 'Successfuly deleted ' + netCount + ' network(s)', 8 | position: 'tr', 9 | autoDismiss: 2 10 | }; 11 | 12 | return SuccessDeleteNotification 13 | } 14 | 15 | export function networksHasErrored(bool) { 16 | return { 17 | type: 'NETWORKS_HAS_ERRORED', 18 | hasErrored: bool 19 | }; 20 | } 21 | 22 | export function networksIsLoading(bool) { 23 | return { 24 | type: 'NETWORKS_IS_LOADING', 25 | isLoading: bool 26 | }; 27 | } 28 | 29 | export function networksFetchDataSuccess(networks) { 30 | return { 31 | type: 'NETWORKS_FETCH_DATA_SUCCESS', 32 | networks 33 | }; 34 | } 35 | 36 | export function networksFetchData(url) { 37 | 38 | return (dispatch) => { 39 | 40 | dispatch(networksIsLoading(true)); 41 | 42 | 43 | Api.get(url) 44 | .then((response) => { 45 | dispatch(networksIsLoading(false)); 46 | return response; 47 | }) 48 | .then((response) => response.json()) 49 | .then((networks) => dispatch(networksFetchDataSuccess(networks))) 50 | .catch(() => dispatch(networksHasErrored(true))); 51 | }; 52 | } 53 | 54 | // POST DATA // 55 | 56 | export function networksPostIsLoading(bool) { 57 | return { 58 | type: 'NETWORKS_POST_IS_LOADING', 59 | isLoading: bool 60 | }; 61 | } 62 | 63 | 64 | export function networksPostHasErrored(bool) { 65 | return { 66 | type: 'NETWORKS_POST_HAS_ERRORED', 67 | hasErrored: bool 68 | }; 69 | } 70 | 71 | 72 | export function networksPostDataSuccess(bool) { 73 | return { 74 | type: 'NETWORKS_POST_DATA_SUCCESS', 75 | isSuccess: bool 76 | }; 77 | } 78 | 79 | 80 | 81 | export function networksPostData(url, networks) { 82 | 83 | return (dispatch) => { 84 | 85 | dispatch(networksPostIsLoading(true)); 86 | 87 | Api.post(url, networks) 88 | .then((response) => { 89 | dispatch(networksPostIsLoading(false)); 90 | 91 | return response; 92 | }) 93 | .then((response) => response.json()) 94 | .then(dispatch(networksPostDataSuccess(true))) 95 | .catch(() => dispatch(networksPostHasErrored(true))); 96 | }; 97 | } 98 | 99 | 100 | // DELETE DATA // 101 | 102 | export function networksDeleteIsLoading(bool) { 103 | return { 104 | type: 'NETWORKS_DELETE_IS_LOADING', 105 | isLoading: bool 106 | }; 107 | } 108 | 109 | 110 | export function networksDeleteHasErrored(bool) { 111 | return { 112 | type: 'NETWORKS_DELETE_HAS_ERRORED', 113 | hasErrored: bool 114 | }; 115 | } 116 | 117 | 118 | export function networksDeleteDataSuccess(bool) { 119 | 120 | return { 121 | type: 'NETWORKS_DELETE_DATA_SUCCESS', 122 | isSuccess: bool 123 | }; 124 | } 125 | 126 | 127 | 128 | export function networksDeleteData(url, networkList) { 129 | 130 | return (dispatch) => { 131 | 132 | dispatch(networksDeleteIsLoading(true)); 133 | 134 | Api.delete(url, networkList) 135 | .then((response) => { 136 | dispatch(networksDeleteIsLoading(false)); 137 | return response; 138 | }) 139 | .then((response) => response.json()) 140 | .then( 141 | dispatch(networksDeleteDataSuccess(true)), 142 | dispatch(info(deleteNetworkNotification(networkList.length))) 143 | ) 144 | .catch(() => dispatch(networksDeleteHasErrored(true))); 145 | }; 146 | } 147 | -------------------------------------------------------------------------------- /web/src/actions/sessions.js: -------------------------------------------------------------------------------- 1 | 2 | import { sessionApi, Api } from '../lib/api'; 3 | import Notifications, { success, error, info } from 'react-notification-system-redux'; 4 | 5 | const loginSuccessNotification = { 6 | title: 'Log-in success', 7 | message: 'Welcome', 8 | position: 'tr', 9 | autoDismiss: 2, 10 | }; 11 | 12 | const loginFailedNotification = { 13 | title: 'Log-in failed', 14 | message: 'Incorrect username/password', 15 | position: 'tr', 16 | autoDismiss: 2, 17 | }; 18 | 19 | const logOutNotification = { 20 | title: 'You logged out', 21 | message: 'See you later !', 22 | position: 'tr', 23 | autoDismiss: 2, 24 | }; 25 | 26 | export function loginSuccess(bool, history) { 27 | history.push('/home/'); 28 | return {type: 'LOG_IN_SUCCESS'} 29 | } 30 | 31 | export function loginFailed(bool) { 32 | return { 33 | type: 'LOG_IN_FAILED', 34 | isFailed: bool 35 | } 36 | } 37 | 38 | export function loginLoading(bool) { 39 | return { 40 | type: 'LOG_IN_LOADING', 41 | isLoading: bool 42 | } 43 | } 44 | 45 | export function logOutUser(history) { 46 | localStorage.removeItem('jwt'); 47 | history.push('/login/'); 48 | return { 49 | type: 'LOG_OUT' 50 | } 51 | } 52 | 53 | 54 | export function logInUser(credentials, history) { 55 | return function(dispatch) { 56 | 57 | dispatch(loginLoading(true)); 58 | 59 | return sessionApi.login(credentials).then(response => { 60 | console.log(response); 61 | if ('access_token' in response) { 62 | 63 | localStorage.setItem('jwt', response.access_token); 64 | dispatch(loginSuccess(true, history)); 65 | dispatch(loginLoading(false)); 66 | dispatch(userFetchData('/auth/me')); 67 | dispatch(success(loginSuccessNotification)); 68 | 69 | } else { 70 | 71 | dispatch(loginLoading(false)); 72 | dispatch(error(loginFailedNotification)); 73 | dispatch(loginFailed(true)); 74 | } 75 | dispatch(loginLoading(false)); 76 | 77 | }).catch(error => { 78 | dispatch(loginLoading(false)); 79 | throw(error); 80 | }); 81 | }; 82 | } 83 | 84 | 85 | // token management 86 | 87 | export function verifyingToken() { 88 | return { 89 | type: 'VERIFYING_TOKEN' 90 | } 91 | } 92 | 93 | export function invalidToken() { 94 | return { 95 | type: 'INVALID_TOKEN' 96 | } 97 | } 98 | 99 | 100 | export function verifyToken() { 101 | return function(dispatch) { 102 | 103 | dispatch(verifyingToken()); 104 | 105 | return sessionApi.verifyToken().then(response => { 106 | 107 | if (response.valid == false) { 108 | 109 | localStorage.removeItem('jwt'); 110 | 111 | } 112 | 113 | }).catch(error => { 114 | 115 | throw(error); 116 | }); 117 | }; 118 | } 119 | 120 | 121 | 122 | // user data retrieving 123 | 124 | 125 | export function userHasErrored(bool) { 126 | return { 127 | type: 'USER_HAS_ERRORED', 128 | hasErrored: bool 129 | }; 130 | } 131 | 132 | export function userIsLoading(bool) { 133 | return { 134 | type: 'USER_IS_LOADING', 135 | isLoading: bool 136 | }; 137 | } 138 | 139 | export function userFetchDataSuccess(user) { 140 | return { 141 | type: 'USER_FETCH_DATA_SUCCESS', 142 | user 143 | }; 144 | } 145 | 146 | 147 | export function userFetchData(url) { 148 | return (dispatch) => { 149 | dispatch(userIsLoading(true)); 150 | 151 | Api.get(url) 152 | .then((response) => { 153 | if (!response.ok) { 154 | throw Error(response.statusText); 155 | } 156 | 157 | dispatch(userIsLoading(false)); 158 | 159 | return response; 160 | }) 161 | .then((response) => response.json()) 162 | .then((user) => dispatch(userFetchDataSuccess(user))) 163 | .catch(() => dispatch(userHasErrored(true))); 164 | }; 165 | } -------------------------------------------------------------------------------- /web/src/components/modals/NewNetworkComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Button, Modal, Icon, Form } from 'semantic-ui-react' 5 | import {ValidateIPaddress} from '../../lib/tools'; 6 | 7 | // Redux 8 | import PropTypes from 'prop-types'; 9 | import { connect } from 'react-redux'; 10 | import { networksPostData } from '../../actions/networks'; 11 | 12 | class NewNetworkComponent extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | this.handleChangeName = this.handleChangeName.bind(this); 16 | this.handleChangeIp = this.handleChangeIp.bind(this); 17 | this.handleOk = this.handleOk.bind(this); 18 | this.handleKeyPress = this.handleKeyPress.bind(this); 19 | this.state = { 20 | modalOpen: false, 21 | name: '', 22 | ip_addr: '' 23 | }; 24 | } 25 | 26 | handleOpen = () => this.setState({ modalOpen: true }) 27 | 28 | handleClose = () => this.setState({ modalOpen: false }) 29 | 30 | 31 | handleChangeName(event) { 32 | this.setState({name: event.target.value}); 33 | } 34 | 35 | handleChangeIp(event) { 36 | this.setState({ip_addr: event.target.value}); 37 | } 38 | 39 | handleOk() { 40 | if (ValidateIPaddress(this.state.ip_addr)) { 41 | this.props.networksPostData('/networks/', this.state) 42 | this.props.fetchData('/networks/'); 43 | this.handleClose(); 44 | } 45 | } 46 | 47 | handleKeyPress = (event) => { 48 | if(event.key == 'Enter'){ 49 | this.handleOk(event); 50 | } 51 | } 52 | 53 | render() { 54 | return ( 55 | } 65 | open={this.state.modalOpen} 66 | onClose={this.handleClose} 67 | size='small'> 68 | New network 69 | 70 |
71 | 72 | 79 | 86 | 87 |
88 |
89 | 90 | 93 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 |
106 | 107 | 108 | Api location 109 | 110 | 111 |
112 | 119 |
120 | 121 | 122 | 123 | 124 | ); 125 | } else { 126 | return ( ); 127 | } 128 | 129 | } 130 | } 131 | 132 | LoginPageComponent.displayName = 'LoginPageComponent'; 133 | 134 | function mapStateToProps(state) { 135 | return { 136 | logged_in: state.sessionReducer, 137 | loginLoading: state.loginLoading 138 | }; 139 | } 140 | 141 | 142 | function mapDispatchToProps(dispatch) { 143 | return { 144 | actions: bindActionCreators(sessionActions, dispatch) 145 | }; 146 | } 147 | 148 | 149 | 150 | export default connect(mapStateToProps, mapDispatchToProps)(LoginPageComponent); -------------------------------------------------------------------------------- /web/src/components/NetworkListComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | 5 | import { Icon, Table, Button, Confirm } from 'semantic-ui-react'; 6 | import NewNetworkComponent from './modals/NewNetworkComponent'; 7 | 8 | require('styles//NetworkList.css'); 9 | 10 | 11 | class NetworkListComponent extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | selectedNetworks: [], 16 | confirmDeleteOpen: false, 17 | result: '' 18 | } 19 | } 20 | 21 | componentWillUpdate(nextProps, nextState) { 22 | if (nextState.result == 'confirmed' && this.state.result != 'confirmed') { 23 | this.props.networksDeleteData('/networks/', this.state.selectedNetworks); 24 | this.setState({selectedNetworks: []}); 25 | this.props.fetchData('/networks/'); 26 | } 27 | } 28 | 29 | showConfirm = () => this.setState({ confirmDeleteOpen: true }) 30 | handleConfirm = () => this.setState({ result: 'confirmed', confirmDeleteOpen: false }) 31 | handleConfirmCancel = () => this.setState({ result: 'cancelled', confirmDeleteOpen: false }) 32 | 33 | onChange(e) { 34 | const selectedNetworks = this.state.selectedNetworks 35 | let index 36 | if (e.target.checked) { 37 | 38 | selectedNetworks.push(e.target.value) 39 | } else { 40 | 41 | index = selectedNetworks.indexOf(e.target.value) 42 | selectedNetworks.splice(index, 1) 43 | } 44 | 45 | this.setState({ selectedNetworks: selectedNetworks, result: '' }) 46 | } 47 | 48 | selectedNetworksCount() { 49 | const result = ' Delete selected ( ' + this.state.selectedNetworks.length + ' )'; 50 | return result 51 | } 52 | 53 | render() { 54 | let content; 55 | 56 | if (this.props.hasErrored) { 57 | content =

API Unavailable

; 58 | return content; 59 | } 60 | 61 | return ( 62 |
63 | 64 | 65 | 66 | 67 | 68 | Name 69 | Subnet 70 | DNS 71 | Type 72 | Hosts 73 | Scan status 74 | 75 | 76 | 77 | 78 | {this.props.networks.results.map((network) => ( 79 | 80 | 81 | 82 | 83 | {network.name} 84 | {network.ip_addr} 85 | {network.dns_names} 86 | {network.is_private ? 'Private' : 'Public'} 87 | {network.host_count} 88 | 89 | 90 | 91 | 0 & network.is_scanning == false) ? 'green' : 'grey' } 96 | /> 97 | 98 | 99 | 100 | ))} 101 | 102 | 103 | 104 | 105 | 106 |
126 | 127 |
128 | ); 129 | } 130 | } 131 | 132 | NetworkListComponent.propTypes = { 133 | 134 | }; 135 | 136 | 137 | NetworkListComponent.displayName = 'NetworkListComponent'; 138 | 139 | export default NetworkListComponent; 140 | -------------------------------------------------------------------------------- /web/src/components/NavbarComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Input, Menu, Icon, Dropdown } from 'semantic-ui-react' 5 | import PropTypes from 'prop-types'; 6 | 7 | // Redux 8 | import { connect } from 'react-redux'; 9 | import { push } from 'react-router-redux' 10 | import { setSearchTerm } from '../actions/search'; 11 | import { searchFetchData } from '../actions/search'; 12 | import { withRouter } from 'react-router-dom' 13 | import { logOutUser } from '../actions/sessions'; 14 | 15 | require('styles//Navbar.css'); 16 | 17 | 18 | class NavbarComponent extends React.Component { 19 | constructor(props) { 20 | super(props); 21 | this.logOut = this.logOut.bind(this); 22 | } 23 | 24 | state = { page: '' } 25 | 26 | componentDidMount() { 27 | const route = this.props.router.location.pathname; 28 | const page = route.split('/'); 29 | 30 | this.setState({ page: page[1] }); 31 | this.searchinput.focus(); 32 | } 33 | 34 | logOut(event) { 35 | event.preventDefault(); 36 | this.props.logOutUser(this.props.history); 37 | } 38 | 39 | navigate(name) { 40 | this.props.pushToHistory('/' + name + '/'); 41 | this.setState({ page: name }); 42 | } 43 | 44 | handleClick = (e, { name }) => this.navigate(name) 45 | 46 | searchOnChange(e, { value }) { 47 | const route = this.props.router.location.pathname; 48 | this.props.setSearchTerm(value); 49 | this.props.fetchData('/search?q=' + value); 50 | 51 | if (route != '/search/'){ 52 | this.navigate('search'); 53 | } 54 | 55 | } 56 | 57 | // Set cursor position to the end 58 | moveCaretAtEnd(e) { 59 | var temp_value = e.target.value 60 | e.target.value = '' 61 | e.target.value = temp_value 62 | } 63 | 64 | render() { 65 | 66 | const { page } = this.state; 67 | 68 | return ( 69 |
70 | 71 | 72 | 76 | NMT 77 | 78 | 79 | Home 80 | 81 | 82 | Networks 83 | 84 | 85 | 86 | 87 | Settings 88 | 89 | 90 | 91 | 92 | { this.searchinput = input; }} 100 | onChange={this.searchOnChange.bind(this)} 101 | onFocus={this.moveCaretAtEnd} /> 102 | 103 | 104 | {this.props.userIsLoading ? 'LOADING' : 105 | 106 | 107 | 108 | 109 | Notifications 110 | 111 | 112 | 113 | Log out 114 | 115 | 116 | 117 | } 118 | 119 | 120 | 121 |
122 | ); 123 | } 124 | } 125 | 126 | 127 | 128 | 129 | 130 | NavbarComponent.propTypes = { 131 | pushToHistory: PropTypes.func.isRequired, 132 | fetchData: PropTypes.func.isRequired, 133 | search: PropTypes.object.isRequired, 134 | hasErrored: PropTypes.bool.isRequired, 135 | isLoading: PropTypes.bool.isRequired, 136 | setSearchTerm: PropTypes.func.isRequired, 137 | searchterm: PropTypes.string.isRequired, 138 | logged_in: PropTypes.bool.isRequired, 139 | userIsLoading: PropTypes.bool.isRequired 140 | }; 141 | 142 | const mapStateToProps = (state) => { 143 | return { 144 | search: state.search, 145 | hasErrored: state.searchHasErrored, 146 | isLoading: state.searchIsLoading, 147 | searchterm: state.searchterm, 148 | router: state.router, 149 | logged_in: state.sessionReducer, 150 | userIsLoading: state.userIsLoading 151 | }; 152 | }; 153 | 154 | const mapDispatchToProps = (dispatch) => { 155 | return { 156 | fetchData: (url) => dispatch(searchFetchData(url)), 157 | setSearchTerm: (term) => dispatch(setSearchTerm(term)), 158 | pushToHistory: (route) => dispatch(push(route)), 159 | logOutUser: (history) => dispatch(logOutUser(history)) 160 | }; 161 | }; 162 | 163 | NavbarComponent.displayName = 'NavbarComponent'; 164 | 165 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(NavbarComponent)); 166 | -------------------------------------------------------------------------------- /shared/iptools.py: -------------------------------------------------------------------------------- 1 | import logging 2 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 3 | 4 | import os, sys 5 | import netifaces as ni 6 | from attrdict import AttrDict 7 | from ipaddress import ip_address, ip_network, IPv4Network, IPv4Interface 8 | from scapy.all import srp, Ether, ARP, conf 9 | from queue import Queue 10 | import threading 11 | from shared.iftools import sensor_ifaces 12 | 13 | import nmap 14 | try: 15 | nm = nmap.PortScanner() 16 | except: 17 | print('Nmap not found', sys.exc_info()[0]) 18 | sys.exit(0) 19 | 20 | 21 | 22 | conf.verbose = 0 23 | 24 | # Check if an ip is in the specified range 25 | 26 | 27 | def in_network(ip_addr, network): 28 | result = False 29 | if ip_address(ip_addr) in ip_network(network, strict=False): 30 | result = True 31 | 32 | return result 33 | 34 | def get_network(ip_addr, netmask): 35 | result = ip_addr 36 | 37 | interface = IPv4Interface(ip_addr+'/'+netmask) 38 | network = interface.network 39 | 40 | network = { 41 | 'name': 'NET_' + str(network), 42 | 'ip_addr': str(network) 43 | } 44 | 45 | return network 46 | 47 | 48 | # Local IP -> MAC 49 | 50 | def local_mac_from_ip(ip): 51 | for i in ni.interfaces(): 52 | addrs = ni.ifaddresses(i) 53 | try: 54 | if_mac = addrs[ni.AF_LINK][0]['addr'] 55 | if_ip = addrs[ni.AF_INET][0]['addr'] 56 | except: 57 | if_mac = if_ip = None 58 | 59 | if if_ip == ip: 60 | return if_mac 61 | return "" 62 | 63 | # Quiet ARP Scanner 64 | 65 | 66 | def arp_scan(iface, network): 67 | results = [] 68 | alive, dead = srp(Ether(dst="ff:ff:ff:ff:ff:ff") / 69 | ARP(pdst=network), timeout=2, verbose=False, iface=iface) 70 | for i in range(0, len(alive)): 71 | mac = alive[i][1].hwsrc 72 | ip = alive[i][1].psrc 73 | 74 | cur_host = { 75 | "ip_addr": ip, 76 | "mac_addr": mac 77 | } 78 | 79 | results.append(AttrDict(cur_host)) 80 | 81 | # Adding the local sensor to the list 82 | for addr in sensor_ifaces(): 83 | if hasattr(addr, 'ip_addr'): 84 | cur_addr = addr['ip_addr'] 85 | if in_network(cur_addr, network): 86 | cur_mac = local_mac_from_ip(cur_addr) 87 | for host in results: 88 | if host['ip_addr'] == cur_addr: 89 | host['mac_addr'] = cur_mac 90 | 91 | local_host = AttrDict( 92 | {"ip_addr": cur_addr, "mac_addr": cur_mac}) 93 | if not local_host in results: 94 | results.append(local_host) 95 | 96 | return results 97 | 98 | 99 | def ping(ip): 100 | return os.system("ping -c 1 " + ip + " >/dev/null 2>&1") == 0 101 | 102 | 103 | def arping(iface, ip): 104 | result = "" 105 | ans, uans = srp(Ether(dst="FF:FF:FF:FF:FF:FF")/ARP(pdst=ip), 106 | timeout=2, inter=0.1, verbose=False, iface=iface) 107 | if len(ans) > 0: 108 | for snd, rcv in ans: 109 | result = rcv.sprintf(r"%Ether.src%") 110 | 111 | return result 112 | 113 | 114 | def ping_scan(iface, network): 115 | 116 | input_queue = Queue() 117 | results = [] 118 | 119 | class PingHostThread(threading.Thread): 120 | 121 | def __init__(self, iface, input_queue, results): 122 | super().__init__() 123 | self.input_queue = input_queue 124 | self.results = results 125 | self.iface = iface 126 | 127 | def run(self): 128 | while True: 129 | ip = self.input_queue.get() 130 | is_alive = ping(ip) 131 | if is_alive: 132 | mac_addr = arping(self.iface, ip) 133 | if len(mac_addr) > 0: 134 | cur_host = { 135 | "ip_addr": ip, 136 | "mac_addr": mac_addr 137 | } 138 | 139 | self.results.append(AttrDict(cur_host)) 140 | self.input_queue.task_done() 141 | 142 | # Start 20 Threads, all are waiting in run -> self.input_queue.get() 143 | for i in range(32): 144 | thread = PingHostThread(iface, input_queue, results) 145 | thread.setDaemon(True) 146 | thread.start() 147 | 148 | # Fill the input queue 149 | for addr in IPv4Network(network, strict=False): 150 | input_queue.put(str(addr)) 151 | 152 | input_queue.join() 153 | 154 | return results 155 | 156 | 157 | 158 | def nmap_host(host, **kwargs): 159 | 160 | tcp_port_range = kwargs.get('tcp_port_range', '22-443') 161 | 162 | services = [] 163 | details = {} 164 | ip_addr = host.ip_addr 165 | 166 | nm.scan(ip_addr, tcp_port_range, 167 | arguments='-sS -sU --max-retries 1 -PN -n -O') 168 | 169 | """ There is a better way to do that """ 170 | for proto in nm[ip_addr].all_protocols(): 171 | 172 | proto_dict = { 173 | 'name': proto, 174 | 'ports': [] 175 | } 176 | 177 | ports = [] 178 | for port in list(nm[ip_addr][proto].keys()): 179 | if nm[ip_addr][proto][port]['state'] == 'open': 180 | if not port in ports: 181 | ports.append(port) 182 | 183 | proto_dict['ports'] = ports 184 | 185 | if not proto_dict in services: 186 | services.append(proto_dict) 187 | 188 | for l in nm[ip_addr]['osmatch']: 189 | 190 | for os in l['osclass']: 191 | 192 | accuracy = int(os['accuracy']) 193 | osfamily = os['osfamily'] 194 | if osfamily != None and accuracy >= 90: 195 | details['osfamily'] = osfamily 196 | details['osgen'] = os['osgen'] 197 | 198 | host.services = services 199 | host.details = details 200 | 201 | return host 202 | 203 | 204 | 205 | if __name__ == "__main__": 206 | # print(ping_scan("192.168.56.0/24")) 207 | print(arping("vboxnet0", "192.168.56.1")) 208 | print(ping_scan('vboxnet0', '192.168.56.0/24')) 209 | print(nmap_host('192.168.40.254')) 210 | -------------------------------------------------------------------------------- /core/database.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | from shared.hashtools import make_shortuuid 4 | import rethinkdb as r 5 | import rethinkdb.errors 6 | from config import db_host, db_port, db_name 7 | 8 | tableList = ['hosts', 'networks', 'sensors', 'users'] 9 | 10 | """ INIT DATABASE """ 11 | 12 | 13 | def make_connection(dbhost, dbport, **kwargs): 14 | conn = r.connect(dbhost, dbport, **kwargs).repl() 15 | return conn 16 | 17 | 18 | def create_database(conn, dbname): 19 | try: 20 | r.db_create(dbname).run(conn) 21 | except rethinkdb.errors.ReqlOpFailedError: 22 | pass 23 | return True 24 | 25 | 26 | def create_table(conn, dbname, tablename): 27 | try: 28 | r.db(dbname).table_create(tablename).run(conn) 29 | except rethinkdb.errors.ReqlOpFailedError: 30 | pass 31 | return True 32 | 33 | 34 | def init_database(dbhost, dbport, dbname, tableList): 35 | conn = make_connection(dbhost, dbport) 36 | create_database(conn, dbname) 37 | 38 | for table in tableList: 39 | create_table(conn, dbname, table) 40 | 41 | conn = make_connection(dbhost, dbport, db=dbname) 42 | return conn 43 | 44 | setup_db = init_database(db_host, db_port, db_name, tableList) 45 | 46 | conn = make_connection(db_host, db_port, db=db_name) 47 | 48 | 49 | 50 | """ INSERT/UPDATE NETWORK """ 51 | 52 | 53 | 54 | def update_network(**kwargs): 55 | 56 | """ Network id is based on network name """ 57 | 58 | ip_addr = kwargs.get('ip_addr') 59 | is_private = kwargs.get('is_private') 60 | name = kwargs.get('name') 61 | dns_names = kwargs.get('dns_names') 62 | is_scanning = kwargs.get('is_scanning', False) 63 | network_id = make_shortuuid(name) 64 | 65 | network = { 66 | 'dns_names': dns_names, 67 | 'ip_addr': ip_addr, 68 | 'is_private' : is_private, 69 | 'name': name, 70 | 'id': network_id, 71 | 'is_scanning': is_scanning, 72 | 'updated_count': 0 73 | 74 | } 75 | 76 | network_exists = r.table("networks").insert([network], conflict="update") 77 | 78 | return network_exists.run(conn) 79 | 80 | 81 | def delete_network(**kwargs): 82 | 83 | network_id = kwargs.get('network_id') 84 | 85 | network = r.table("networks").filter({'id': network_id}) 86 | 87 | return network.delete().run(conn) 88 | 89 | """ INSERT NEW HOST """ 90 | """ Host_id is based on mac_address""" 91 | 92 | 93 | def new_host(**kwargs): 94 | 95 | mac_addr = kwargs.get('mac_addr') 96 | 97 | host = kwargs 98 | host['id'] = make_shortuuid(mac_addr) 99 | 100 | r.table("hosts").insert([host], conflict="update").run(conn) 101 | 102 | return True 103 | 104 | 105 | """ GET ALL NETWORKS """ 106 | 107 | 108 | def get_networks(**kwargs): 109 | 110 | network_id = kwargs.get('network_id') 111 | ip_addr = kwargs.get('ip_addr', False) 112 | 113 | networks = r.table('networks') 114 | 115 | if network_id: 116 | networks = networks.filter({'id': network_id}) 117 | 118 | if ip_addr: 119 | networks = networks.filter({'ip_addr': ip_addr}) 120 | 121 | networks = networks.order_by(r.desc('name')).run(conn) 122 | 123 | 124 | return list(networks) 125 | 126 | 127 | """ GET ALL HOSTS FROM THE SPECIFIED NETWORK ID""" 128 | 129 | 130 | def get_hosts(**kwargs): 131 | 132 | 133 | network_id = kwargs.get('network_id') 134 | text_query = kwargs.get('text_query') 135 | host_id = kwargs.get('host_id') 136 | 137 | hosts = r.table('hosts') 138 | 139 | if network_id: 140 | hosts = hosts.filter({'network_id': network_id}) 141 | 142 | if host_id: 143 | hosts = hosts.filter({'id': host_id}) 144 | 145 | if text_query: 146 | hosts = hosts.filter( 147 | lambda doc: (doc['dns_names'].match(text_query)) | ( 148 | doc['ip_addr'].match(text_query)) 149 | 150 | ) 151 | 152 | hosts = hosts.order_by('ip_addr').run(conn) 153 | 154 | return list(hosts) 155 | 156 | 157 | """ SENSORS PART """ 158 | """ GET ALL SENSORS""" 159 | 160 | def get_sensors(**kwargs): 161 | 162 | 163 | sensors = r.table('sensors').run(conn) 164 | 165 | return list(sensors) 166 | 167 | 168 | """ INSERT/UPDATE SENSOR """ 169 | 170 | 171 | def update_sensor(**kwargs): 172 | 173 | ip_addr = kwargs.get('ip_addr') 174 | is_private = kwargs.get('is_private') 175 | name = kwargs.get('name') 176 | dns_names = kwargs.get('dns_names') 177 | interfaces = kwargs.get('interfaces', []) 178 | 179 | sensor = { 180 | 181 | 'dns_names': dns_names, 182 | 'ip_addr': ip_addr, 183 | 'is_private' : is_private, 184 | 'name': name, 185 | 'id': make_shortuuid(name), 186 | 'interfaces': interfaces 187 | 188 | } 189 | 190 | r.table("sensors").insert([sensor], conflict="update").run(conn) 191 | 192 | return True 193 | 194 | 195 | """" USERS PART """ 196 | 197 | def get_user(**kwargs): 198 | email = kwargs.get('email') 199 | user_id = kwargs.get('user_id') 200 | 201 | users = r.table('users') 202 | 203 | if email: 204 | users = users.filter({'email': email}) 205 | 206 | if user_id: 207 | users = users.filter({'id': user_id}) 208 | 209 | users = users.run(conn) 210 | user = list(users) 211 | 212 | if len(user)>0: 213 | user = user[0] 214 | else: 215 | user = None 216 | 217 | return user 218 | 219 | 220 | def get_users(**kwargs): 221 | email = kwargs.get('email') 222 | user_id = kwargs.get('user_id') 223 | 224 | users = r.table('users') 225 | 226 | if email: 227 | users = users.filter({'email': email}) 228 | 229 | if user_id: 230 | users = users.filter({'id': user_id}) 231 | 232 | users = users.run(conn, read_mode="outdated") 233 | 234 | return list(users) 235 | 236 | 237 | def add_user(**kwargs): 238 | 239 | email = kwargs.get('email') 240 | password = kwargs.get('password') 241 | 242 | user = { 243 | 244 | 'email': email, 245 | 'password': password, 246 | } 247 | 248 | user_exists = get_users(email=email) 249 | if len(user_exists) == 0: 250 | return r.table("users").insert([user], conflict="update").run(conn) 251 | 252 | return True 253 | 254 | 255 | 256 | def server_status(): 257 | if r.db('rethinkdb').table('server_status').run(conn): 258 | return True 259 | else: 260 | return False 261 | -------------------------------------------------------------------------------- /sensor/sensor.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | import logging 4 | import argparse 5 | from queue import Queue 6 | from attrdict import AttrDict 7 | from shared import api_wrapper as apiwr 8 | from shared import console 9 | from shared.iptools import arp_scan, ping_scan, nmap_host, get_network 10 | from shared.dnstools import reverse_lookup, get_domain_name 11 | from shared.iftools import which_iface, sensor_ifaces 12 | from attrdict import AttrDict 13 | from datetime import datetime 14 | from config import api_url, sensor_name, tcp_port_range, login, password 15 | 16 | 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument('--no-portscan', help='disable port scan', action="store_true") 19 | parser.add_argument('--no-icmp', help='disable ICMP scan', action="store_true") 20 | parser.add_argument('--filter', help='Scan only selected network') 21 | parser.add_argument('--show-networks', help='Display all networks provided by the API', action="store_true") 22 | parser.add_argument('--discover', help='Auto-detect networks', action="store_true") 23 | 24 | args = parser.parse_args() 25 | 26 | 27 | ''' Sensor object ''' 28 | 29 | class Sensor(object): 30 | """ Sensor object """ 31 | 32 | def __init__(self, api_url, **kwargs): 33 | self.api = self.connect(api_url) 34 | self.interfaces = sensor_ifaces() 35 | self.portscan = kwargs.get('portscan', True) 36 | self.icmpscan = kwargs.get('icmpscan', True) 37 | self.filter = kwargs.get('filter', None) 38 | self.networks = self.api.get_networks(filter=self.filter) 39 | self.local_networks = [] 40 | self.update() 41 | 42 | def connect(self, api_url): 43 | """ Connection to the api with the api_wrapper lib """ 44 | 45 | print('\n------------------------------') 46 | try: 47 | console.write('Checking API connectivity at ' + api_url + "...", 3) 48 | api = apiwr.CoreApi( 49 | url=api_url, 50 | login=login, 51 | password=password 52 | ) 53 | console.write('Successfully connected to ' + api_url + "...", 0) 54 | print('------------------------------\n') 55 | 56 | except BaseException: 57 | console.write('Unable to communicate with the API at ' + api_url, 2) 58 | print('------------------------------\n') 59 | sys.exit(2) 60 | 61 | return api 62 | 63 | 64 | def update(self): 65 | self.api.update_sensor(ip_addr=self.interfaces[0].ip_addr, 66 | name=sensor_name, 67 | dns_names='sensor1.local', 68 | interfaces=self.interfaces) 69 | 70 | 71 | def full_scan(self, network): 72 | network_ip = network['ip_addr'] 73 | 74 | # Choose interface for performing the scan 75 | interface = which_iface(network_ip) 76 | console.write('Using interface: {}'.format(interface), 3) 77 | 78 | updated_network = AttrDict({ 79 | "hosts": [], 80 | "dns_names": "" 81 | }) 82 | 83 | hosts = [] 84 | 85 | # 1 - do arp scan 86 | console.write('Performing ARP Scan', 3) 87 | arphosts = arp_scan(interface, network_ip) 88 | if len(arphosts) > 0: 89 | console.write('Found {} Hosts'.format(len(arphosts)), 4) 90 | 91 | # 2 - do icmp scan 92 | # Fusion the two objects (pingscan[] and arpscan[]) 93 | 94 | pinghosts = [] 95 | if self.icmpscan: 96 | console.write('Performing ICMP Scan', 3) 97 | pinghosts = ping_scan(interface, network_ip) 98 | 99 | hosts = {x['mac_addr']: x for x in arphosts + pinghosts}.values() 100 | 101 | # Resolve each found host threaded ? 102 | for host in hosts: 103 | 104 | # Get current date 105 | host.last_seen = datetime.now().isoformat() 106 | 107 | """ dns """ 108 | host.dns_names = reverse_lookup(host.ip_addr) 109 | console.write('Performing Reverse lookup on {} -> {}'.format(host.ip_addr, host.dns_names), 3) 110 | """""" 111 | 112 | """ PORT SCAN (uses nmap) """ 113 | host.services = [] 114 | if self.portscan: 115 | msg = 'Performing Service discovery on {} / TCP: {}'.format(str(host.ip_addr), tcp_port_range) 116 | console.write(msg, 3) 117 | host = nmap_host( 118 | host, port_range=tcp_port_range) 119 | """""" 120 | 121 | """ Get mac vendor """ 122 | macvendor = self.api.get_macvendor(host.mac_addr) 123 | console.write( 124 | "Resolving MAC vendor for {} -> {}".format(host.ip_addr, macvendor), 3) 125 | host['mac_vendor'] = macvendor 126 | """""" 127 | 128 | 129 | host['network_id'] = network['id'] 130 | 131 | self.api.add_host(**host) 132 | 133 | # Get the network domain name if possible 134 | if hasattr(updated_network, 'dns_names'): 135 | if '.' in host.dns_names: 136 | domain_name = get_domain_name(host.dns_names) 137 | if len(domain_name) > len(updated_network['dns_names']): 138 | updated_network.dns_names = domain_name 139 | 140 | updated_network['hosts'].append(host) 141 | 142 | return updated_network 143 | 144 | 145 | def main(): 146 | """ Main program """ 147 | sensor = Sensor(api_url, 148 | portscan=(not args.no_portscan), 149 | icmpscan=(not args.no_icmp), 150 | filter=args.filter) 151 | 152 | returned_networks = len(sensor.networks) 153 | if returned_networks == 0: 154 | msg = "No networks were returned by the API. You can add networks on the web interface." 155 | console.write(msg, 3) 156 | 157 | sys.exit(0) 158 | 159 | api = sensor.api 160 | 161 | if args.no_icmp: 162 | console.write('ICMP scan disabled', 3) 163 | 164 | if args.no_portscan: 165 | console.write('Port scan disabled', 3) 166 | 167 | if args.discover: 168 | 169 | net_count = 0 170 | 171 | for i in sensor.interfaces: 172 | network = get_network(i.ip_addr, i.netmask) 173 | api.update_network(**network) 174 | msg = 'Detected {} - {}'.format(network['name'], 175 | network['ip_addr']) 176 | console.write(msg, 3) 177 | net_count += 1 178 | 179 | msg = 'Added/updated {} network(s) to the api'.format(net_count) 180 | console.write(msg, 0) 181 | sys.exit(0) 182 | 183 | if args.show_networks: 184 | console.write('Network list:', 3) 185 | for network in sensor.networks: 186 | print(network['name'], network['ip_addr']) 187 | sys.exit(0) 188 | 189 | # Retreive networks from the API 190 | for network in sensor.networks: 191 | 192 | start = datetime.now() 193 | 194 | msg = "Starting scan on: {} - {}".format( 195 | network['name'], network['ip_addr']) 196 | 197 | console.write(msg, 3) 198 | 199 | network['is_scanning'] = True 200 | api.update_network(**network) 201 | scan_result = sensor.full_scan(network) 202 | 203 | network['is_scanning'] = False 204 | network['dns_names'] = scan_result.dns_names 205 | 206 | api.update_network(**network) 207 | 208 | hosts = scan_result.hosts 209 | 210 | end = datetime.now() 211 | elapsed = (end - start) / 60 212 | 213 | console.write("Scan finnished, {} host(s) found. Elapsed time: {}".format( 214 | len(hosts), elapsed), 0) 215 | print('\n------------------------------') 216 | 217 | if __name__ == "__main__": 218 | main() 219 | -------------------------------------------------------------------------------- /core/api.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import sys 4 | import logging 5 | from config import debug, production 6 | from sanic import Sanic 7 | from sanic import response 8 | from sanic_cors import CORS 9 | 10 | # Blueprints for swagger ( REMOVE IN PRODUCTION MODE) 11 | if not production: 12 | from sanic_openapi import swagger_blueprint, openapi_blueprint 13 | 14 | from sanic_openapi import doc 15 | 16 | import json 17 | import database as db 18 | from shared import mactools 19 | from time import sleep 20 | from shared.mltools import is_gateway 21 | from shared.console import write 22 | from utils.utils import is_private, list_plugins 23 | 24 | from sanic_jwt import initialize 25 | from sanic_jwt import exceptions 26 | from sanic_jwt.decorators import protected 27 | 28 | 29 | # Get script paths 30 | pathname = os.path.dirname(sys.argv[0]) 31 | fullpath = os.path.abspath(pathname) 32 | 33 | """ DEFINE THE APP """ 34 | 35 | app = Sanic(strict_slashes=True) 36 | 37 | """ OPENAPI/ SWAGGER BLUEPRINTS """ 38 | 39 | if not production: 40 | # Blueprints for swagger ( REMOVE IN PRODUCTION MODE) 41 | app.blueprint(openapi_blueprint) 42 | app.blueprint(swagger_blueprint) 43 | 44 | 45 | """ AUTH FUNCTIONS """ 46 | 47 | async def authenticate(request, *args, **kwargs): 48 | """ This function is used for checking 49 | user/password from the database """ 50 | user = None 51 | 52 | email = request.json.get('email', None) 53 | password = request.json.get('password', None) 54 | 55 | if not email or not password: 56 | raise exceptions.AuthenticationFailed("Missing email or password.") 57 | 58 | user = db.get_user(email=email) 59 | 60 | if (user is None) or (password != user['password']): 61 | raise exceptions.AuthenticationFailed("Incorrect user/password") 62 | 63 | return user 64 | 65 | 66 | def retrieve_user(request, payload, *args, **kwargs): 67 | if payload: 68 | user_id = payload.get('user_id', None) 69 | user = db.get_user(user_id=user_id) 70 | 71 | return user 72 | else: 73 | return None 74 | 75 | initialize(app, 76 | authenticate, 77 | retrieve_user=retrieve_user 78 | ) 79 | 80 | 81 | """ CORS """ 82 | 83 | cors = CORS(app, resources={r"*": {"origins": "*"}}) 84 | 85 | @doc.summary("Returns route METHODS") 86 | @app.middleware('request') 87 | async def method_interception(request): 88 | if request.method == 'OPTIONS': 89 | return response.json( 90 | {}, 91 | headers={'Access-Control-Max-Age': 3600}, 92 | status=200 93 | ) 94 | 95 | 96 | """ HOSTS """ 97 | 98 | def count_ports(host): 99 | svc_count = 0 100 | 101 | for proto in host['services']: 102 | for port in proto['ports']: 103 | svc_count += 1 104 | 105 | host['open_ports'] = svc_count 106 | 107 | return host 108 | 109 | @doc.summary("Show host details") 110 | @app.route("/hosts//", methods=['GET']) 111 | @protected() 112 | async def get_host(request, host_id): 113 | 114 | results = { 115 | "host": { 116 | } 117 | } 118 | 119 | hosts = db.get_hosts(host_id=host_id) 120 | 121 | if len(hosts) > 0: 122 | host = hosts[0] 123 | host = count_ports(host) 124 | 125 | results['host'] = host 126 | results['host']['network'] = db.get_networks( 127 | network_id=host['network_id'])[0] 128 | 129 | return response.json(results) 130 | 131 | 132 | """ NETWORKS """ 133 | 134 | 135 | @doc.summary("Returns all discovered networks") 136 | @app.route("/networks/", methods=['GET']) 137 | @protected() 138 | async def get_networks(request): 139 | 140 | results = { 141 | "results": [] 142 | } 143 | 144 | args = request.args 145 | filters = None 146 | 147 | if 'filter' in args: 148 | filters = args['filter'][0] 149 | 150 | networks = db.get_networks(ip_addr=filters) 151 | 152 | total = len(networks) 153 | 154 | if total >0: 155 | 156 | for network in networks: 157 | 158 | network_id = network['id'] 159 | network['host_count'] = len(db.get_hosts(network_id=network_id)) 160 | results['results'].append(network) 161 | 162 | results['total'] = total 163 | 164 | return response.json(results) 165 | 166 | 167 | @doc.summary("Updates networks") 168 | @app.route("/networks/", methods=['POST']) 169 | @protected() 170 | async def update_networks(request): 171 | 172 | results = {} 173 | status_code = 201 174 | 175 | if request.json: 176 | json_query = request.json 177 | 178 | if type(json_query) != dict: 179 | json_query = json.loads(json_query) 180 | 181 | isprivate = is_private(json_query['ip_addr']) 182 | json_query['is_private'] = isprivate 183 | 184 | db.update_network(**json_query) 185 | results = {"status": "created"} 186 | 187 | else: 188 | results = {"status": "error"} 189 | status_code = 500 190 | 191 | 192 | 193 | results = response.json(results, status=status_code) 194 | 195 | return results 196 | 197 | 198 | @doc.summary("Deletes an array of networkIds") 199 | @app.route("/networks/", methods=['DELETE']) 200 | @protected() 201 | async def delete_network_list(request): 202 | 203 | results = { 204 | "status": "deleted" 205 | } 206 | 207 | args = request.args 208 | filters = None 209 | 210 | if request.json: 211 | json_query = request.json 212 | for network_id in json_query: 213 | db.delete_network(network_id=network_id) 214 | 215 | return response.json(results) 216 | 217 | 218 | 219 | @doc.summary("Return all hosts in the selected network_id") 220 | @app.route("/networks//", methods=['GET']) 221 | @protected() 222 | async def get_network_hosts(request, network_id): 223 | 224 | results = { 225 | "network": { 226 | }, 227 | "results": [] 228 | } 229 | 230 | hosts = db.get_hosts(network_id=network_id) 231 | 232 | for h in hosts: 233 | h = count_ports(h) 234 | results['results'].append(h) 235 | 236 | network = db.get_networks(network_id=network_id) 237 | 238 | results['network'] = network[0] 239 | 240 | results['total'] = len(hosts) 241 | 242 | return response.json(results) 243 | 244 | 245 | @doc.summary("Delete the specified network") 246 | @app.route("/networks//", methods=['DELETE']) 247 | @protected() 248 | async def delete_network(request, network_id): 249 | 250 | results = {} 251 | 252 | db.delete_network(network_id=network_id) 253 | results = {"status": "deleted"} 254 | 255 | return response.json(results, status=200) 256 | 257 | 258 | @doc.summary("Updates hosts in the selected network_id") 259 | @app.route("/networks//", methods=['POST']) 260 | @protected() 261 | async def update_network_hosts(request, network_id): 262 | results = {} 263 | 264 | json_query = request.json 265 | 266 | if type(json_query) != dict: 267 | json_query = json.loads(json_query) 268 | if not 'details' in json_query.keys(): 269 | json_query['details'] = {} 270 | 271 | db.new_host(**json_query) 272 | results = {"status": "created"} 273 | 274 | return response.json(results, status=201) 275 | 276 | 277 | """ SENSORS """ 278 | 279 | 280 | @doc.summary("Returns all sensors") 281 | @app.route("/sensors/", methods=['GET']) 282 | @protected() 283 | async def get_sensors(request): 284 | sensors = db.get_sensors() 285 | results = { 286 | "total": len(sensors), 287 | "results": sensors 288 | } 289 | 290 | return response.json(results) 291 | 292 | 293 | @doc.summary("Updates sensors") 294 | @app.route("/sensors/", methods=['POST']) 295 | @protected() 296 | async def update_sensors(request): 297 | 298 | results = {} 299 | 300 | json_query = request.json 301 | 302 | if type(json_query) != dict: 303 | json_query = json.loads(json_query) 304 | 305 | db.update_sensor(**json_query) 306 | results = {"status": "created"} 307 | 308 | return response.json(results, status=201) 309 | 310 | 311 | """ CHECK """ 312 | 313 | 314 | @doc.summary("Check if api respond") 315 | @app.route("/check", methods=['GET']) 316 | async def check_api(request): 317 | results = { 318 | "status": "success" 319 | } 320 | 321 | return response.json(results) 322 | 323 | 324 | """ METRICS """ 325 | 326 | 327 | @doc.summary("Metrics route") 328 | @app.route("/metrics/", methods=['GET']) 329 | @protected() 330 | async def get_metrics(request): 331 | hosts = db.get_hosts() 332 | ports_count = 0 333 | 334 | for host in hosts: 335 | for service in host['services']: 336 | ports_count += 1 337 | 338 | host_count = len(hosts) 339 | 340 | results = { 341 | "host_count": host_count, 342 | "network_count": len(db.get_networks()), 343 | "sensor_count": len(db.get_sensors()), 344 | "ports_count": ports_count 345 | } 346 | 347 | return response.json(results) 348 | 349 | 350 | """ GRAPHS """ 351 | 352 | 353 | def last_digit(ip_addr): 354 | result = ip_addr.split('.')[-1] 355 | result = "." + result 356 | return result 357 | 358 | def edge(src, dst): 359 | _edge = {"from": src, "to": dst} 360 | return _edge 361 | 362 | 363 | @doc.summary("Returns graphs v2") 364 | @app.route("/graphs/", methods=['GET']) 365 | @app.route("/graphs//", methods=['GET']) 366 | @protected() 367 | async def get_graphs(request, network_id=None): 368 | 369 | results = { 370 | "nodes": [], 371 | "edges": [] 372 | } 373 | filters = { 374 | "network_id": network_id 375 | } 376 | 377 | for network in db.get_networks(**filters): 378 | 379 | gateway = {} 380 | 381 | hosts = db.get_hosts(network_id=network['id']) 382 | if len(hosts) > 0: 383 | 384 | for host in hosts: 385 | if is_gateway(host): 386 | gateway = host 387 | 388 | # Make host label 389 | label = "{}\n {}".format( 390 | last_digit(host['ip_addr']), 391 | host['dns_names'] 392 | ) 393 | 394 | # Make host id + network_id etc.. 395 | node = {"id": host['id'], "label": label, "group": host['network_id']} 396 | results["nodes"].append(node) 397 | 398 | if gateway != {}: 399 | for host in hosts: 400 | if host != gateway: 401 | if host['network_id'] == gateway['network_id']: 402 | host_to_gw = edge(host['id'], gateway['id']) 403 | results['edges'].append(host_to_gw) 404 | 405 | 406 | return response.json(results) 407 | 408 | 409 | """ USERS """ 410 | 411 | 412 | @doc.summary("Get all users") 413 | @app.route("/users/", methods=['GET']) 414 | @protected() 415 | async def get_users(request): 416 | 417 | results = { 418 | "results": [] 419 | } 420 | users = db.get_users() 421 | for u in users: 422 | u.pop('password', None) 423 | 424 | results['results'] = users 425 | return response.json(results, status=200) 426 | 427 | 428 | 429 | """ MAC """ 430 | 431 | 432 | @doc.summary("Resolve mac address's vendor") 433 | @app.route("/macvendor//", methods=['GET']) 434 | @protected() 435 | async def get_macvendor(request, mac_addr): 436 | 437 | results = { 438 | "mac_addr": mac_addr, 439 | "vendor": mactools.get_mac_vendor(mac_addr) 440 | } 441 | 442 | return response.json(results, status=200) 443 | 444 | 445 | """ SUGGESTIONS/SEARCH """ 446 | 447 | 448 | @doc.summary("Search endpoint") 449 | @app.route("/search", methods=['GET']) 450 | @protected() 451 | async def search(request): 452 | query = {} 453 | text_query = request.args.get('q') 454 | 455 | if text_query: 456 | query['text_query'] = text_query 457 | 458 | hosts = db.get_hosts(**query) 459 | 460 | results = { 461 | "total": len(hosts), 462 | "results": hosts 463 | } 464 | 465 | return response.json(results) 466 | 467 | 468 | """ PLUGINS """ 469 | 470 | plugin_list = list_plugins(fullpath + '/data/plugins') 471 | 472 | @doc.summary("List all available plugins and versions") 473 | @app.route("/plugins/", methods=['GET']) 474 | @protected() 475 | async def get_plugins(request): 476 | query = {} 477 | 478 | 479 | results = { 480 | "total": len(plugin_list), 481 | "results": plugin_list 482 | } 483 | 484 | return response.json(results) 485 | 486 | 487 | if __name__ == "__main__": 488 | 489 | 490 | app.config.LOGO = False 491 | app.config.SANIC_JWT_USER_ID = 'id' 492 | app.config.SANIC_JWT_SECRET = 'gkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI9Gl6VRM+2s8CAg' 493 | app.config.SANIC_JWT_EXPIRATION_DELTA = 28800 494 | # app.config.SANIC_JWT_EXPIRATION_DELTA = 30 495 | 496 | logging.getLogger('sanic_cors').level = logging.DEBUG 497 | 498 | write("Starting API...", 3) 499 | 500 | if production: 501 | write("Production mode enabled", 0) 502 | else: 503 | write("Production mode disabled", 1) 504 | 505 | while db.server_status() != True: 506 | write("Waiting for the database to be UP...", 3) 507 | sleep(1) 508 | db.add_user(email='admin', password='password') 509 | write("Added default user admin/password to database", 0) 510 | 511 | app.run(host="0.0.0.0", port=8080, debug=debug) 512 | --------------------------------------------------------------------------------