Commit 297a524fd43fbd3e916a49042777b42bb8862162
Exists in
master
Persistência dos ativos de rede num banco local
Showing
11 changed files
with
148 additions
and
25 deletions
Show diff stats
cocar/__init__.py
| ... | ... | @@ -7,7 +7,6 @@ import ConfigParser |
| 7 | 7 | import logging |
| 8 | 8 | import logging.config |
| 9 | 9 | from sqlalchemy.engine import create_engine |
| 10 | -from sqlalchemy.ext.declarative import declarative_base | |
| 11 | 10 | from sqlalchemy.orm import scoped_session, sessionmaker |
| 12 | 11 | |
| 13 | 12 | |
| ... | ... | @@ -46,11 +45,8 @@ class Cocar(object): |
| 46 | 45 | # SQLAlchemy |
| 47 | 46 | sqlalchemy_url = self.config.get('sqlalchemy', 'url') |
| 48 | 47 | self.engine = create_engine(sqlalchemy_url, echo=True) |
| 49 | - self.Base = declarative_base() | |
| 50 | - self.Base.metadata.bind = self.engine | |
| 51 | - self.session = scoped_session( | |
| 48 | + self.Session = scoped_session( | |
| 52 | 49 | sessionmaker(bind=self.engine, |
| 53 | - autocommit=True, | |
| 54 | - #expire_on_commit=False | |
| 55 | - ) | |
| 56 | - ) | |
| 50 | + autocommit=True | |
| 51 | + ) | |
| 52 | + ) | |
| 57 | 53 | \ No newline at end of file | ... | ... |
cocar/model/__init__.py
cocar/model/computer.py
| 1 | 1 | #!/bin/env python |
| 2 | 2 | # -*- coding: utf-8 -*- |
| 3 | 3 | __author__ = 'eduardo' |
| 4 | - | |
| 4 | +from sqlalchemy.schema import Column | |
| 5 | +from sqlalchemy.types import * | |
| 6 | +from sqlalchemy import ForeignKey | |
| 5 | 7 | from .host import Host |
| 6 | 8 | |
| 7 | 9 | |
| ... | ... | @@ -9,6 +11,12 @@ class Computer(Host): |
| 9 | 11 | """ |
| 10 | 12 | Ativo de rede identificado como estação de trabalho |
| 11 | 13 | """ |
| 14 | + __tablename__ = 'computador' | |
| 15 | + network_ip = Column(String(16), ForeignKey("host.network_ip"), nullable=False, primary_key=True) | |
| 16 | + so_name = Column(String) | |
| 17 | + so_version = Column(String) | |
| 18 | + accuracy = Column(Integer) | |
| 19 | + | |
| 12 | 20 | def __init__(self, |
| 13 | 21 | so, |
| 14 | 22 | *args, |
| ... | ... | @@ -19,4 +27,10 @@ class Computer(Host): |
| 19 | 27 | :param so: Sistema Operacional encontrado |
| 20 | 28 | """ |
| 21 | 29 | Host.__init__(self, *args, **kwargs) |
| 22 | - self.so = so | |
| 23 | 30 | \ No newline at end of file |
| 31 | + self.so = so | |
| 32 | + | |
| 33 | + #SQLAlchemy parameters | |
| 34 | + os_elm = self.so.items()[0] | |
| 35 | + self.so_name = os_elm[1]['osfamily'] | |
| 36 | + self.so_version = os_elm[1]['version'] | |
| 37 | + self.accuracy = os_elm[1]['accuracy'] | |
| 24 | 38 | \ No newline at end of file | ... | ... |
cocar/model/host.py
| ... | ... | @@ -2,12 +2,23 @@ |
| 2 | 2 | # -*- coding: utf-8 -*- |
| 3 | 3 | __author__ = 'eduardo' |
| 4 | 4 | from netaddr import IPAddress |
| 5 | +from sqlalchemy.schema import Column | |
| 6 | +from sqlalchemy.types import String, Integer | |
| 7 | +from . import Base | |
| 5 | 8 | |
| 6 | 9 | |
| 7 | -class Host(object): | |
| 10 | +class Host(Base): | |
| 8 | 11 | """ |
| 9 | 12 | Classe que define um ativo de rede |
| 10 | 13 | """ |
| 14 | + __tablename__ = 'host' | |
| 15 | + network_ip = Column(String(16), primary_key=True, nullable=False) | |
| 16 | + mac_address = Column(String(18)) | |
| 17 | + name = Column(String) | |
| 18 | + inclusion_date = Column(String(20)) | |
| 19 | + scantime = Column(Integer) | |
| 20 | + ports = Column(String) | |
| 21 | + | |
| 11 | 22 | def __init__(self, |
| 12 | 23 | ip_address, |
| 13 | 24 | mac_address=None, |
| ... | ... | @@ -32,4 +43,25 @@ class Host(object): |
| 32 | 43 | self.hostname = hostname |
| 33 | 44 | self.inclusion_date = inclusion_date |
| 34 | 45 | self.scantime = scantime |
| 35 | - self.open_ports = open_ports | |
| 36 | 46 | \ No newline at end of file |
| 47 | + self.open_ports = open_ports | |
| 48 | + | |
| 49 | + # Parâmetros do SQLAlchemy | |
| 50 | + self.network_ip = str(self.ip_address) | |
| 51 | + self.ports = ','.join(map(str, self.open_ports.keys())) | |
| 52 | + if len(self.hostname.values()) > 0: | |
| 53 | + self.name = self.hostname.values()[0] | |
| 54 | + else: | |
| 55 | + self.name = None | |
| 56 | + | |
| 57 | + def __repr__(self): | |
| 58 | + """ | |
| 59 | + Metodo que passa a lista de parametros da classe | |
| 60 | + """ | |
| 61 | + return "<Host('%s, %s, %s, %s, %s, %s')>" % ( | |
| 62 | + self.network_ip, | |
| 63 | + self.mac_address, | |
| 64 | + self.name, | |
| 65 | + self.inclusion_date, | |
| 66 | + self.scantime, | |
| 67 | + self.ports | |
| 68 | + ) | |
| 37 | 69 | \ No newline at end of file | ... | ... |
cocar/model/network.py
| ... | ... | @@ -4,14 +4,25 @@ __author__ = 'eduardo' |
| 4 | 4 | import os.path |
| 5 | 5 | from .. import Cocar |
| 6 | 6 | from netaddr import IPNetwork, IPSet |
| 7 | +from ..model import Base | |
| 8 | +from sqlalchemy.schema import Column | |
| 9 | +from sqlalchemy.types import String, Integer | |
| 7 | 10 | |
| 8 | 11 | |
| 9 | -class Network(Cocar): | |
| 12 | +class Network(Base): | |
| 10 | 13 | """ |
| 11 | 14 | Rede onde a busca será realizada |
| 12 | 15 | """ |
| 16 | + __tablename__ = 'network' | |
| 17 | + ip_network = Column(String(16), nullable=False, primary_key=True) | |
| 18 | + network_file = Column(String) | |
| 19 | + netmask = Column(String(16)) | |
| 20 | + prefixlen = Column(Integer) | |
| 21 | + name = Column(String) | |
| 22 | + | |
| 13 | 23 | def __init__(self, |
| 14 | 24 | network_ip, |
| 25 | + network_file=None, | |
| 15 | 26 | netmask=None, |
| 16 | 27 | prefixlen=None, |
| 17 | 28 | name=None |
| ... | ... | @@ -22,17 +33,19 @@ class Network(Cocar): |
| 22 | 33 | :param cidr: CIDR para calcular a máscara da rede |
| 23 | 34 | :param name: Nome da rede |
| 24 | 35 | """ |
| 25 | - Cocar.__init__(self) | |
| 26 | 36 | self.network_ip = IPNetwork(network_ip) |
| 27 | 37 | self.netmask = netmask |
| 28 | 38 | self.prefixlen = prefixlen |
| 29 | 39 | self.name = name |
| 30 | - self.network_file = self.cocar_data_dir + "/" + str(self.network_ip.ip) + ".xml" | |
| 40 | + self.network_file = network_file | |
| 31 | 41 | if self.netmask is None: |
| 32 | 42 | self.netmask = self.network_ip.netmask |
| 33 | 43 | if self.prefixlen is None: |
| 34 | 44 | self.prefixlen = self.network_ip.prefixlen |
| 35 | 45 | |
| 46 | + # SQLAlchemy attribute | |
| 47 | + self.ip_network = str(self.network_ip.ip) | |
| 48 | + | |
| 36 | 49 | def ip_list(self): |
| 37 | 50 | """ |
| 38 | 51 | Método que encontra a lista de IP's da subrede | ... | ... |
cocar/model/printer.py
| ... | ... | @@ -3,16 +3,26 @@ |
| 3 | 3 | __author__ = 'eduardo' |
| 4 | 4 | |
| 5 | 5 | from .host import Host |
| 6 | +from sqlalchemy import ForeignKey | |
| 7 | +from sqlalchemy.schema import Column | |
| 8 | +from sqlalchemy.types import String, Integer | |
| 6 | 9 | |
| 7 | 10 | |
| 8 | 11 | class Printer(Host): |
| 9 | 12 | """ |
| 10 | 13 | Classe que identifica uma impressora |
| 11 | 14 | """ |
| 15 | + __tablename__ = 'printer' | |
| 16 | + network_ip = Column(String(16), ForeignKey("host.network_ip"), nullable=False, primary_key=True) | |
| 17 | + counter = Column(Integer) | |
| 18 | + serial = Column(String(50)) | |
| 19 | + description = Column(String) | |
| 20 | + | |
| 12 | 21 | def __init__(self, |
| 13 | 22 | counter=None, |
| 14 | 23 | model=None, |
| 15 | 24 | serial=None, |
| 25 | + description=None, | |
| 16 | 26 | *args, |
| 17 | 27 | **kwargs |
| 18 | 28 | ): |
| ... | ... | @@ -24,4 +34,5 @@ class Printer(Host): |
| 24 | 34 | Host.__init__(self, *args, **kwargs) |
| 25 | 35 | self.counter = counter |
| 26 | 36 | self.model = model |
| 27 | - self.serial = serial | |
| 28 | 37 | \ No newline at end of file |
| 38 | + self.serial = serial | |
| 39 | + self.description = description | |
| 29 | 40 | \ No newline at end of file | ... | ... |
cocar/tests/__init__.py
| ... | ... | @@ -4,6 +4,7 @@ __author__ = 'eduardo' |
| 4 | 4 | from .. import Cocar |
| 5 | 5 | import os |
| 6 | 6 | import os.path |
| 7 | +from ..model import Base | |
| 7 | 8 | |
| 8 | 9 | cocar = Cocar(environment='test') |
| 9 | 10 | test_dir = os.path.dirname(os.path.realpath(__file__)) |
| ... | ... | @@ -13,7 +14,7 @@ def setup_package(): |
| 13 | 14 | """ |
| 14 | 15 | Setup test data for the package |
| 15 | 16 | """ |
| 16 | - cocar.Base.metadata.create_all(cocar.engine) | |
| 17 | + Base.metadata.create_all(cocar.engine) | |
| 17 | 18 | pass |
| 18 | 19 | |
| 19 | 20 | |
| ... | ... | @@ -21,5 +22,5 @@ def teardown_package(): |
| 21 | 22 | """ |
| 22 | 23 | Remove test data |
| 23 | 24 | """ |
| 24 | - cocar.Base.metadata.drop_all(cocar.engine) | |
| 25 | + Base.metadata.drop_all(cocar.engine) | |
| 25 | 26 | pass |
| 26 | 27 | \ No newline at end of file | ... | ... |
cocar/tests/test_discover.py
cocar/tests/test_identify.py
| ... | ... | @@ -60,8 +60,9 @@ class TestIdentify(unittest.TestCase): |
| 60 | 60 | self.assertIsInstance(computer, Computer) |
| 61 | 61 | |
| 62 | 62 | # Se é um computer, tenho que identificar o SO |
| 63 | - os_elm = computer.so.items()[0] | |
| 64 | - self.assertEqual(os_elm[1]['osfamily'], 'Linux') | |
| 63 | + self.assertEqual(computer.so_name, 'Linux') | |
| 64 | + self.assertEqual(computer.so_version, 'Linux 3.7 - 3.9') | |
| 65 | + self.assertEqual(computer.accuracy, '98') | |
| 65 | 66 | |
| 66 | 67 | def test_identify_printer(self): |
| 67 | 68 | """ | ... | ... |
cocar/tests/test_persistence.py
| ... | ... | @@ -6,6 +6,8 @@ import unittest |
| 6 | 6 | import cocar.tests |
| 7 | 7 | from ..xml_utils import NmapXML |
| 8 | 8 | from ..model.computer import Computer |
| 9 | +from ..model.printer import Printer | |
| 10 | +from ..model.network import Network | |
| 9 | 11 | |
| 10 | 12 | |
| 11 | 13 | class TestPersistence(unittest.TestCase): |
| ... | ... | @@ -20,12 +22,13 @@ class TestPersistence(unittest.TestCase): |
| 20 | 22 | self.network_file = cocar.tests.test_dir + "/fixtures/192.168.0.0-24.xml" |
| 21 | 23 | self.localhost_file = cocar.tests.test_dir + "/fixtures/127.0.0.1.xml" |
| 22 | 24 | self.printer_file = cocar.tests.test_dir + "/fixtures/printer.xml" |
| 25 | + self.session = cocar.tests.cocar.Session | |
| 23 | 26 | |
| 24 | 27 | def test_connect(self): |
| 25 | 28 | """ |
| 26 | 29 | Testa conexão do SQLAlchemy |
| 27 | 30 | """ |
| 28 | - db_session = cocar.tests.cocar.session | |
| 31 | + db_session = self.session | |
| 29 | 32 | self.assertIsNotNone(db_session) |
| 30 | 33 | |
| 31 | 34 | def test_persist_computer(self): |
| ... | ... | @@ -40,8 +43,54 @@ class TestPersistence(unittest.TestCase): |
| 40 | 43 | computer = nmap_xml.identify_host(hostname) |
| 41 | 44 | self.assertIsInstance(computer, Computer) |
| 42 | 45 | |
| 46 | + # Agora testa a persistência | |
| 47 | + self.session.add(computer) | |
| 48 | + self.session.flush() | |
| 49 | + | |
| 50 | + # Tenta ver se gravou | |
| 51 | + results = self.session.query(Computer).first() | |
| 52 | + self.assertIsNotNone(results) | |
| 53 | + | |
| 54 | + def test_persist_printer(self): | |
| 55 | + """ | |
| 56 | + Grava impressora no banco de dados | |
| 57 | + """ | |
| 58 | + hostname = '10.72.168.3' | |
| 59 | + nmap_xml = NmapXML(self.printer_file) | |
| 60 | + host = nmap_xml.parse_xml() | |
| 61 | + assert host | |
| 62 | + | |
| 63 | + printer = nmap_xml.identify_host(hostname) | |
| 64 | + self.assertIsInstance(printer, Printer) | |
| 65 | + | |
| 66 | + # Agora testa a persistência | |
| 67 | + self.session.add(printer) | |
| 68 | + self.session.flush() | |
| 69 | + | |
| 70 | + # Tenta ver se gravou | |
| 71 | + results = self.session.query(Printer).first() | |
| 72 | + self.assertIsNotNone(results) | |
| 73 | + | |
| 74 | + def test_persist_network(self): | |
| 75 | + """ | |
| 76 | + Testa gravação dos dados de rede | |
| 77 | + """ | |
| 78 | + rede = Network( | |
| 79 | + network_ip='192.168.0.0', | |
| 80 | + netmask='255.255.255.0', | |
| 81 | + network_file='/tmp/network.xml', | |
| 82 | + name='Rede de Teste' | |
| 83 | + ) | |
| 84 | + self.session.add(rede) | |
| 85 | + self.session.flush() | |
| 86 | + | |
| 87 | + # Tenta ver se gravou | |
| 88 | + results = self.session.query(Network).first() | |
| 89 | + self.assertIsNotNone(results) | |
| 90 | + | |
| 43 | 91 | def tearDown(self): |
| 44 | 92 | """ |
| 45 | 93 | Remove dados |
| 46 | 94 | """ |
| 95 | + self.session.close() | |
| 47 | 96 | pass |
| 48 | 97 | \ No newline at end of file | ... | ... |
cocar/xml_utils.py
| ... | ... | @@ -69,7 +69,8 @@ class NmapXML(object): |
| 69 | 69 | 'vendor': osclass.get('vendor'), |
| 70 | 70 | 'osfamily': osclass.get('osfamily'), |
| 71 | 71 | 'accuracy': osclass.get('accuracy'), |
| 72 | - 'cpe': osclass.findtext('cpe') | |
| 72 | + 'cpe': osclass.findtext('cpe'), | |
| 73 | + 'version': osmatch.get('name') | |
| 73 | 74 | } |
| 74 | 75 | |
| 75 | 76 | # General attributes |
| ... | ... | @@ -86,11 +87,12 @@ class NmapXML(object): |
| 86 | 87 | |
| 87 | 88 | # Ordena os sistemas operacionais por accuracy |
| 88 | 89 | host = self.hosts[hostname] |
| 89 | - accuracy = 0 | |
| 90 | + accuracy = int(0) | |
| 90 | 91 | if host.get('os'): |
| 91 | 92 | # Nesse caso já sei que é computador. Precisa identificar o OS |
| 92 | 93 | for os in host['os'].keys(): |
| 93 | 94 | if int(host['os'][os]['accuracy']) > accuracy: |
| 95 | + accuracy = int(host['os'][os]['accuracy']) | |
| 94 | 96 | os_final = os |
| 95 | 97 | |
| 96 | 98 | scantime = int(host.get('endtime')) - int(host.get('starttime')) | ... | ... |