mirror of
https://github.com/lopes/netbox-scanner.git
synced 2025-07-20 20:34:38 +02:00
core routines added
This commit is contained in:
parent
e7c53e0236
commit
c64d9bb369
23
README.md
23
README.md
@ -5,23 +5,6 @@ A scanner util for NetBox, because certain networks can be updated automagically
|
|||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
|
|
||||||
- Python 3
|
* Python 3
|
||||||
- Postgres 10
|
* Postgres 10.5
|
||||||
|
* Nmap 7.60
|
||||||
Python modules:
|
|
||||||
|
|
||||||
- sqlalchemy
|
|
||||||
- psycopg2
|
|
||||||
- python-netbox
|
|
||||||
- python-nmap
|
|
||||||
|
|
||||||
netbox-scanner/
|
|
||||||
- README.md
|
|
||||||
- requirements.txt
|
|
||||||
- setup.py
|
|
||||||
- Makefile
|
|
||||||
- netbox-scanner/
|
|
||||||
--- nbs.py
|
|
||||||
--- db.py
|
|
||||||
--- config.py
|
|
||||||
--- __init__.py
|
|
||||||
|
@ -8,20 +8,17 @@ NETBOX = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DATABASE = {
|
DATABASE = {
|
||||||
'NAME': 'nbscanner', # database name
|
'NAME': 'nbscan', # database name
|
||||||
'USER': 'nbscanner', # postgresql user
|
'USER': 'nbscan', # postgresql user
|
||||||
'PASSWORD': 'abc123', # postgresql password
|
'PASSWORD': '', # postgresql password
|
||||||
'HOST': 'localhost', # database server
|
'HOST': 'localhost', # database server
|
||||||
'PORT': '5432', # database port
|
'PORT': '5432', # database port
|
||||||
}
|
}
|
||||||
|
|
||||||
SESSION_LENGTH = 16 # length of scan's session token
|
TAGS = ['auto'] # only 1 tag is allowed
|
||||||
DISABLE_TLS_WARNINGS = True # should urllib stop displaying TLS/SSL warnings?
|
UNKNOWN_HOSTNAME = 'UNKNOWN HOST'
|
||||||
|
DISABLE_TLS_WARNINGS = True # stop displaying TLS/SSL warnings?
|
||||||
|
|
||||||
# These are the targets to be scanned. It could be:
|
# These are the targets to be scanned.
|
||||||
# - single hosts: 10.2.50.7
|
# Example: ['192.168.40.0/20', '10.2.50.0/24']
|
||||||
# - single networks: 10.2.50.0/24
|
|
||||||
# - some hosts: 10.2.50.1-7
|
|
||||||
# The syntax is just the same as used in Nmap.
|
|
||||||
# Example: ['10.2.50.7', '10.2.50.0/24']
|
|
||||||
TARGETS = []
|
TARGETS = []
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
# postgres=# CREATE DATABASE nbscanner;
|
|
||||||
# CREATE DATABASE
|
|
||||||
# postgres=# CREATE USER nbscanner WITH PASSWORD 'abc123';
|
|
||||||
# CREATE ROLE
|
|
||||||
# postgres=# GRANT ALL PRIVILEGES ON DATABASE nbscanner TO nbscanner;
|
|
||||||
# GRANT
|
|
||||||
# postgres=# \q
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# https://www.pythoncentral.io/introductory-tutorial-python-sqlalchemy/
|
|
||||||
# http://www.rmunn.com/sqlalchemy-tutorial/tutorial.html
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from sqlalchemy import create_engine, Column, Integer, String
|
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
|
|
||||||
|
|
||||||
class Host(db.Model):
|
|
||||||
__tablename__ = 'Hosts'
|
|
||||||
id = self.con.Column(self.con.Integer, primary_key=True)
|
|
||||||
date = self.con.Column(self.con.DateTime, nullable=False)
|
|
||||||
session = self.con.Column(self.con.String(self.session_length), nullable=False)
|
|
||||||
address = self.con.Column(self.con.String(100), nullable=False)
|
|
||||||
name = self.con.Column(self.con.String(255))
|
|
||||||
mac = self.con.Column(self.con.String(17))
|
|
||||||
vendor = self.con.Column(self.con.String(100))
|
|
||||||
osvendor = self.con.Column(self.con.String(100))
|
|
||||||
osfamily = self.con.Column(self.con.String(100))
|
|
||||||
cpe = self.con.Column(self.con.String(255))
|
|
||||||
description = self.con.Column(self.con.String(100))
|
|
||||||
|
|
||||||
def __init__(d, s, a, n, m, v, ov, of, c, desc):
|
|
||||||
self.date = d
|
|
||||||
self.session = s
|
|
||||||
self.address = a
|
|
||||||
self.name = n
|
|
||||||
self.mac = m
|
|
||||||
self.vendor = v
|
|
||||||
self.osvendor = ov
|
|
||||||
self.osfamily = of
|
|
||||||
self.cpe = c
|
|
||||||
self.description = desc
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'Host(addr={}, date={}, name={})'.format(self.address,
|
|
||||||
self.date, self.name)
|
|
||||||
|
|
||||||
|
|
||||||
base = declarative_base()
|
|
||||||
engine = create_engine('postgresql://{}:{}@{}:{}/{}'.format(pguser,pgpass,pghost,pgport,pgdb))
|
|
||||||
base.metadata.create_all(engine)
|
|
44
netbox-scanner/models.py
Normal file
44
netbox-scanner/models.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# postgres=# CREATE DATABASE nbscan;
|
||||||
|
# CREATE DATABASE
|
||||||
|
# postgres=# CREATE USER nbscan WITH PASSWORD 'abc123';
|
||||||
|
# CREATE ROLE
|
||||||
|
# postgres=# GRANT ALL PRIVILEGES ON DATABASE nbscan TO nbscan;
|
||||||
|
# GRANT
|
||||||
|
# postgres=# \q
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
from sqlalchemy import create_engine, Column, Integer, String, DateTime
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
|
from config import DATABASE
|
||||||
|
|
||||||
|
|
||||||
|
base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
class Host(base):
|
||||||
|
__tablename__ = 'Hosts'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
date = Column(DateTime, nullable=False)
|
||||||
|
network = Column(String(120), nullable=False)
|
||||||
|
address = Column(String(100), nullable=False)
|
||||||
|
name = Column(String(255))
|
||||||
|
cpe = Column(String(255))
|
||||||
|
|
||||||
|
def __init__(self, d, net, a, n, c):
|
||||||
|
self.date = d
|
||||||
|
self.network = net
|
||||||
|
self.address = a
|
||||||
|
self.name = n
|
||||||
|
self.cpe = c
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<Host: address={}, network={}, name={}>'.format(self.address,
|
||||||
|
self.network, self.name)
|
||||||
|
|
||||||
|
|
||||||
|
engine = create_engine('postgresql://{}:{}@{}:{}/{}'.format(DATABASE['USER'],
|
||||||
|
DATABASE['PASSWORD'], DATABASE['HOST'], DATABASE['PORT'],
|
||||||
|
DATABASE['NAME']))
|
||||||
|
base.metadata.create_all(engine)
|
56
netbox-scanner/nbsync.py
Normal file
56
netbox-scanner/nbsync.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from urllib3 import disable_warnings
|
||||||
|
from urllib3.exceptions import InsecureRequestWarning
|
||||||
|
from ipaddress import IPv4Network
|
||||||
|
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from netbox import NetBox
|
||||||
|
from netbox.exceptions import NotFoundException
|
||||||
|
|
||||||
|
from models import Host, base, engine
|
||||||
|
from config import TAGS
|
||||||
|
|
||||||
|
|
||||||
|
class NetBoxSync(object):
|
||||||
|
|
||||||
|
def __init__(self, host, tls, token, port, warnings):
|
||||||
|
self.netbox = NetBox(host=host, use_ssl=tls, auth_token=token,
|
||||||
|
port=port)
|
||||||
|
if warnings:
|
||||||
|
disable_warnings(InsecureRequestWarning)
|
||||||
|
base.metadata.bind = engine
|
||||||
|
dbsession = sessionmaker(bind=engine)
|
||||||
|
self.session = dbsession()
|
||||||
|
|
||||||
|
def get_last_scan_date(self):
|
||||||
|
return self.session.query(Host).order_by(Host.date.desc()).first().date
|
||||||
|
|
||||||
|
def get_last_scan_all(self):
|
||||||
|
return self.session.query(Host).filter(Host.date == self.get_last_scan_date()).all()
|
||||||
|
|
||||||
|
def get_last_scan_networks(self):
|
||||||
|
return self.session.query(Host.network).filter(Host.date == self.get_last_scan_date()).group_by(Host.network).all()
|
||||||
|
|
||||||
|
def in_last_scan(self, address):
|
||||||
|
return self.session.query(Host.address).filter(Host.address == address).all()
|
||||||
|
|
||||||
|
def sync(self):
|
||||||
|
# updating netbox according to last scan
|
||||||
|
for host in self.get_last_scan_all():
|
||||||
|
nbhost = self.netbox.ipam.get_ip_addresses(address=host.address)
|
||||||
|
if nbhost:
|
||||||
|
if (TAGS[0] in nbhost[0]['tags']) and (host.name != nbhost[0]['description']):
|
||||||
|
self.netbox.ipam.update_ip('{}/32'.format(host.address), description=host.name)
|
||||||
|
else:
|
||||||
|
self.netbox.ipam.create_ip_address('{}/32'.format(host.address), tags=TAGS, description=host.name)
|
||||||
|
|
||||||
|
# deleting not found ipv4 hosts
|
||||||
|
for net in self.get_last_scan_networks():
|
||||||
|
for ipv4 in IPv4Network(net[0]):
|
||||||
|
address = str(ipv4)
|
||||||
|
if not self.in_last_scan(address):
|
||||||
|
nbhost = self.netbox.ipam.get_ip_addresses(address=address)
|
||||||
|
try:
|
||||||
|
if TAGS[0] in nbhost[0]['tags']:
|
||||||
|
self.netbox.ipam.delete_ip_address(address)
|
||||||
|
except IndexError:
|
||||||
|
pass
|
@ -1,110 +1,17 @@
|
|||||||
from random import SystemRandom
|
from netscan import NetworkScanner
|
||||||
from string import ascii_lowercase, digits
|
from nbsync import NetBoxSync
|
||||||
from urllib3 import disable_warnings
|
from config import NETBOX, DISABLE_TLS_WARNINGS, TARGETS
|
||||||
from urllib3.exceptions import InsecureRequestWarning
|
|
||||||
|
|
||||||
from nmap import PortScanner
|
from datetime import datetime
|
||||||
from netbox import NetBox
|
|
||||||
from netbox.exceptions import NotFoundException
|
|
||||||
|
|
||||||
from config import NETBOX, DATABASE, SESSION_LENGTH, DISABLE_TLS_WARNINGS, TARGETS
|
print('{} - starting scan'.format(datetime.now()))
|
||||||
|
ns = NetworkScanner()
|
||||||
|
ns.scan(TARGETS)
|
||||||
|
|
||||||
|
print('{} - starting sync'.format(datetime.now()))
|
||||||
|
nbs = NetBoxSync(NETBOX['ADDRESS'], NETBOX['TLS'], NETBOX['TOKEN'],
|
||||||
|
NETBOX['PORT'], DISABLE_TLS_WARNINGS)
|
||||||
|
nbs.sync()
|
||||||
|
|
||||||
class NetBoxScanner(object):
|
print('{} - finished'.format(datetime.now()))
|
||||||
|
exit(0)
|
||||||
def __init__(self):
|
|
||||||
self.netbox = NetBox(host=NETBOX['address'],
|
|
||||||
use_ssl=NETBOX['TLS'], auth_token=NETBOX['TOKEN'],
|
|
||||||
port=NETBOX['PORT'])
|
|
||||||
self.networks = TARGETS
|
|
||||||
self.results = None
|
|
||||||
self.session = ''.join(SystemRandom().choice(ascii_lowercase + digits)
|
|
||||||
for _ in range(SESSION_LENGTH))
|
|
||||||
if DISABLE_TLS_WARNINGS:
|
|
||||||
disable_warnings(InsecureRequestWarning)
|
|
||||||
|
|
||||||
def scan(self):
|
|
||||||
'''
|
|
||||||
Return pattern:
|
|
||||||
{
|
|
||||||
'networks': [
|
|
||||||
{
|
|
||||||
'network': '10.2.50.0/25',
|
|
||||||
'hosts': [
|
|
||||||
{
|
|
||||||
'address': '10.2.50.7',
|
|
||||||
'mac': 'ff:ff:ff:ff:ff:ff',
|
|
||||||
'vendor': 'Dell',
|
|
||||||
'name': 'hostname',
|
|
||||||
'osvendor': 'Microsoft',
|
|
||||||
'osfamily': 'Windows',
|
|
||||||
'cpe': []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
self.results = {'networks':[]}
|
|
||||||
for net in self.networks:
|
|
||||||
nm = PortScanner()
|
|
||||||
nm.scan(net, arguments='-T4 -O -F')
|
|
||||||
hosts = []
|
|
||||||
for host in nm.all_hosts():
|
|
||||||
ipv4 = nm[host]['addresses']['ipv4']
|
|
||||||
name = nm[host]['hostnames'][0]['name']
|
|
||||||
try:
|
|
||||||
mac = nm[host]['addresses']['mac']
|
|
||||||
vendor = nm[host]['addresses']['vendor'][mac]
|
|
||||||
except KeyError:
|
|
||||||
mac = vendor = '-'
|
|
||||||
try:
|
|
||||||
osvendor = nm[host]['osmatch'][0]['osclass'][0]['vendor']
|
|
||||||
osfamily = nm[host]['osmatch'][0]['osclass'][0]['osfamily']
|
|
||||||
cpe = nm[host]['osmatch'][0]['osclass'][0]['cpe']
|
|
||||||
except (KeyError, IndexError):
|
|
||||||
osvendor = osfamily = cpe = '-'
|
|
||||||
hosts.append({'address':ipv4,'mac':mac,'vendor':vendor,
|
|
||||||
'name':name,'osvendor':osvendor,'osfamily':osfamily,
|
|
||||||
'cpe':cpe})
|
|
||||||
self.results['networks'].append({'network':net,'hosts':hosts})
|
|
||||||
return self.results
|
|
||||||
|
|
||||||
def nbquery(self, address):
|
|
||||||
addr = self.netbox.ipam.get_ip_address(address)
|
|
||||||
print(addr); return
|
|
||||||
try:
|
|
||||||
print(addr[0]['address'])
|
|
||||||
print(addr[0]['description'])
|
|
||||||
print(addr[0]['tags'])
|
|
||||||
print(addr[0]['status']['label'])
|
|
||||||
print(addr[0]['last_updated'])
|
|
||||||
except IndexError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def nbdelete(self, address):
|
|
||||||
try:
|
|
||||||
self.netbox.ipam.delete_ip_address(address)
|
|
||||||
except NotFoundException:
|
|
||||||
return None
|
|
||||||
return address
|
|
||||||
|
|
||||||
def nbcreate(self, address, **kwargs):
|
|
||||||
'''nbs.nbcreate('10.2.50.77', tags=["auto"], description="Desktop")
|
|
||||||
'''
|
|
||||||
self.netbox.ipam.create_ip_address(address, **kwargs)
|
|
||||||
|
|
||||||
def nbupdate(self, address, **kwargs):
|
|
||||||
self.netbox.ipam.update_ip(address, **kwargs)
|
|
||||||
|
|
||||||
def sync(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
nbs = NetBoxScanner()
|
|
||||||
|
|
||||||
#print(nbs.session)
|
|
||||||
#nbs.nbquery('10.2.50.99')
|
|
||||||
#nbs.nbdelete('10.2.50.99')
|
|
||||||
#nbs.nbcreate('10.2.50.99', tags=["auto"], description="Desktop")
|
|
||||||
#nbs.nbupdate('10.2.50.99', description="Server")
|
|
||||||
|
53
netbox-scanner/netscan.py
Normal file
53
netbox-scanner/netscan.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from random import SystemRandom
|
||||||
|
from string import ascii_lowercase, digits
|
||||||
|
|
||||||
|
from nmap import PortScanner
|
||||||
|
from cpe import CPE
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
from models import Host, base, engine
|
||||||
|
from config import UNKNOWN_HOSTNAME
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkScanner(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
base.metadata.bind = engine
|
||||||
|
dbsession = sessionmaker(bind=engine)
|
||||||
|
self.session = dbsession()
|
||||||
|
|
||||||
|
def get_name(self, nbcpe):
|
||||||
|
cpe = CPE(nbcpe[0], CPE.VERSION_2_3)
|
||||||
|
return '{}:{}:{}'.format(cpe.get_vendor()[0], cpe.get_product()[0],
|
||||||
|
cpe.get_version()[0])
|
||||||
|
|
||||||
|
def scan(self, targets):
|
||||||
|
''''''
|
||||||
|
date = datetime.now()
|
||||||
|
host_count = 0
|
||||||
|
|
||||||
|
for net in targets:
|
||||||
|
|
||||||
|
nm = PortScanner()
|
||||||
|
nm.scan(net, arguments='-T4 -O -F')
|
||||||
|
|
||||||
|
for host in nm.all_hosts():
|
||||||
|
addr = nm[host]['addresses']['ipv4']
|
||||||
|
name = nm[host]['hostnames'][0]['name']
|
||||||
|
try:
|
||||||
|
cpe = nm[host]['osmatch'][0]['osclass'][0]['cpe']
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
cpe = None
|
||||||
|
if not name:
|
||||||
|
try:
|
||||||
|
name = self.get_name(cpe)
|
||||||
|
except TypeError:
|
||||||
|
name = UNKNOWN_HOSTNAME
|
||||||
|
self.session.add(Host(date, net, addr, name, cpe))
|
||||||
|
|
||||||
|
host_count += 1
|
||||||
|
if host_count >= 300000: # ~ a /14 network
|
||||||
|
self.session.commit()
|
||||||
|
host_count = 0
|
||||||
|
self.session.commit()
|
11
requirements.txt
Normal file
11
requirements.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
certifi==2018.8.24
|
||||||
|
chardet==3.0.4
|
||||||
|
cpe==1.2.1
|
||||||
|
idna==2.7
|
||||||
|
ipaddress==1.0.22
|
||||||
|
psycopg2==2.7.5
|
||||||
|
python-netbox==0.0.12
|
||||||
|
python-nmap==0.6.1
|
||||||
|
requests==2.19.1
|
||||||
|
SQLAlchemy==1.2.11
|
||||||
|
urllib3==1.23
|
16
setup.py
Normal file
16
setup.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
from codecs import open
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='netbox-scanner',
|
||||||
|
packages=['netbox-scanner'],
|
||||||
|
version='0.0.2',
|
||||||
|
description='',
|
||||||
|
long_description=open('README.txt',encoding='utf-8').read(),
|
||||||
|
author='José Lopes de Oliveira Jr.',
|
||||||
|
license='MIT License',
|
||||||
|
url='https://github.com/forkd/netbox-scanner',
|
||||||
|
keywords=['netbox','ipam','network','scanner']
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user