Commit 45d477ecea855227b17ea3b81b94bd5f2b456b06

Authored by Eduardo Santos
Committed by Eduardo Santos
2 parents b5bad4eb 2160d465
Exists in master

Primeira versão do módulo capaz de realizar a descoberta da rede

@@ -43,3 +43,7 @@ coverage.xml @@ -43,3 +43,7 @@ coverage.xml
43 docs/_build/ 43 docs/_build/
44 # PyBuilder 44 # PyBuilder
45 target/ 45 target/
  46 +
  47 +# Pycharm
  48 +.idea
  49 +*.ini
@@ -9,4 +9,15 @@ Para funcionar é necessário primeiro instalar o pacote da distribuição e só @@ -9,4 +9,15 @@ Para funcionar é necessário primeiro instalar o pacote da distribuição e só
9 9
10 <pre> 10 <pre>
11 virtualenv --system-site-packages -p /usr/bin/python2.7 cocar-agente 11 virtualenv --system-site-packages -p /usr/bin/python2.7 cocar-agente
  12 +</pre>
  13 +
  14 +Dependência de um módulo externo chamado pylanos
  15 +
  16 +<pre>
  17 +cd /home/eduardo/srv/cocar-agente/src
  18 +cd cocar-agente/cocar
  19 +mkdir lib
  20 +cd lib
  21 +wget https://github.com/c0r3dump3d/pylanos/raw/master/PyLanOS.py
  22 +mv PyLanOS.py pylanos.py
12 </pre> 23 </pre>
13 \ No newline at end of file 24 \ No newline at end of file
cocar/__init__.py
  1 +#!/bin/env python
  2 +# -*- coding: utf-8 -*-
1 __author__ = 'eduardo' 3 __author__ = 'eduardo'
  4 +import os
  5 +
  6 +import ConfigParser
  7 +import logging
  8 +import logging.config
  9 +
  10 +config = ConfigParser.ConfigParser()
  11 +here = os.path.abspath(os.path.dirname(__file__))
  12 +config_file = os.path.join(here, '../development.ini')
  13 +config.read(config_file)
  14 +
  15 +# Logging
  16 +logging.config.fileConfig(config_file)
  17 +
  18 +class Cocar(object):
  19 + """
  20 + Classe global com as configurações
  21 + """
  22 +
  23 + def __init__(self):
  24 + """
  25 + Parâmetro construtor
  26 + """
  27 + cocar_data_dir = config.get('cocar', 'data_dir')
  28 +
  29 + if os.path.isdir(cocar_data_dir):
  30 + self.cocar_data_dir = cocar_data_dir
  31 + else:
  32 + os.mkdir(cocar_data_dir)
  33 + self.cocar_data_dir = cocar_data_dir
2 \ No newline at end of file 34 \ No newline at end of file
cocar/host.py
@@ -1,57 +0,0 @@ @@ -1,57 +0,0 @@
1 -#!/bin/env python  
2 -# -*- coding: utf-8 -*-  
3 -# Inspired by the code in http://www.copyandwaste.com/posts/view/multiprocessing-snmp-with-python/  
4 -__author__ = 'eduardo'  
5 -  
6 -import netsnmp  
7 -  
8 -  
9 -  
10 -class Host(object):  
11 - """  
12 - Creates a host record  
13 - """  
14 -  
15 - def __init__(self,  
16 - hostname=None,  
17 - query=None):  
18 - self.hostname = hostname  
19 - self.query = query  
20 -  
21 -  
22 -class SnmpSession():  
23 - """A SNMP Session"""  
24 - def __init__(self,  
25 - oid=".1.3.6.1.2.1.1.1.0",  
26 - iid=None,  
27 - Version=2,  
28 - DestHost="localhost",  
29 - Community="public",  
30 - Verbose=True,  
31 - ):  
32 - self.oid = oid  
33 - self.Version = Version  
34 - self.DestHost = DestHost  
35 - self.Community = Community  
36 - self.Verbose = Verbose  
37 - self.var = netsnmp.Varbind(oid, iid)  
38 - self.hostrec = Host()  
39 - self.hostrec.hostname = self.DestHost  
40 -  
41 - def query(self):  
42 - """Creates SNMP query  
43 -  
44 - Fills out a Host Object and returns result  
45 - """  
46 - try:  
47 - result = netsnmp.snmpget(self.var,  
48 - Version=self.Version,  
49 - DestHost=self.DestHost,  
50 - Community=self.Community)  
51 - self.hostrec.query = result  
52 - except Exception, err:  
53 - if self.Verbose:  
54 - print err  
55 - self.hostrec.query = None  
56 - finally:  
57 - return self.hostrec  
58 \ No newline at end of file 0 \ No newline at end of file
cocar/model/__init__.py 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +__author__ = 'eduardo'
cocar/model/computer.py 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +#!/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +__author__ = 'eduardo'
  4 +
  5 +from .host import Host
  6 +
  7 +
  8 +class Computer(Host):
  9 + """
  10 + Ativo de rede identificado como estação de trabalho
  11 + """
  12 + def __init__(self,
  13 + so
  14 + ):
  15 + """
  16 + Classe que identifica uma estação de trabalho
  17 + :param so: Sistema Operacional encontrado
  18 + """
  19 + Host.__init__(self)
  20 + self.so = so
0 \ No newline at end of file 21 \ No newline at end of file
cocar/model/host.py 0 → 100644
@@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
  1 +#!/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +__author__ = 'eduardo'
  4 +from netaddr import IPAddress
  5 +
  6 +
  7 +class Host(object):
  8 + """
  9 + Classe que define um ativo de rede
  10 + """
  11 + def __init__(self,
  12 + ip_address,
  13 + mac_address,
  14 + network,
  15 + hostname=None,
  16 + inclusion_date=None,
  17 + scantime=None,
  18 + open_ports=[]):
  19 + """
  20 + Método construtor do ativo de rede
  21 +
  22 + :param ip_address: Endereço Ip
  23 + :param mac_address: MAC
  24 + :param network: Endereço da rede onde o ativo foi encontrado
  25 + :param hostname: Nome do host
  26 + :param inclusion_date: Data de coleta
  27 + :param scantime: Tempo levado na execução
  28 + :param open_ports: Portas abertas
  29 + :return:
  30 + """
  31 + self.ip_address = IPAddress(ip_address)
  32 + self.mac_address = mac_address
  33 + self.network = network
  34 + self.hostname = hostname
  35 + self.inclusion_date = inclusion_date
  36 + self.scantime = scantime
  37 + self.open_ports = open_ports
0 \ No newline at end of file 38 \ No newline at end of file
cocar/model/network.py 0 → 100644
@@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
  1 +#!/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +__author__ = 'eduardo'
  4 +import os.path
  5 +from .. import Cocar
  6 +from netaddr import IPNetwork, IPSet
  7 +
  8 +
  9 +class Network(Cocar):
  10 + """
  11 + Rede onde a busca será realizada
  12 + """
  13 + def __init__(self,
  14 + network_ip,
  15 + netmask=None,
  16 + prefixlen=None,
  17 + name=None
  18 + ):
  19 + """
  20 + :param network_ip: Ip da rede
  21 + :param netmask: Máscara da rede
  22 + :param cidr: CIDR para calcular a máscara da rede
  23 + :param name: Nome da rede
  24 + """
  25 + Cocar.__init__(self)
  26 + self.network_ip = IPNetwork(network_ip)
  27 + self.netmask = netmask
  28 + self.prefixlen = prefixlen
  29 + self.name = name
  30 + self.network_dir = self.cocar_data_dir + "/" + str(self.network_ip.ip)
  31 + # Cria diretório se não existir
  32 + if not os.path.isdir(self.network_dir):
  33 + os.mkdir(self.network_dir)
  34 +
  35 + if self.netmask is None:
  36 + self.netmask = self.network_ip.netmask
  37 + if self.prefixlen is None:
  38 + self.prefixlen = self.network_ip.prefixlen
  39 +
  40 + def ip_list(self):
  41 + """
  42 + Método que encontra a lista de IP's da subrede
  43 + :return: Conjunto de IP's para realizar a interação
  44 + """
  45 + return IPSet(self.network_ip)
0 \ No newline at end of file 46 \ No newline at end of file
cocar/model/printer.py 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +#!/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +__author__ = 'eduardo'
  4 +
  5 +from .host import Host
  6 +
  7 +
  8 +class Printer(Host):
  9 + """
  10 + Classe que identifica uma impressora
  11 + """
  12 + def __init__(self,
  13 + counter,
  14 + model=None,
  15 + serial=None
  16 + ):
  17 + """
  18 + :param counter: Contador da impressora
  19 + :param model: Modelo da impressora
  20 + :param serial: Número de série da impressora
  21 + """
  22 + Host.__init__(self)
  23 + self.counter = counter
  24 + self.model = model
  25 + self.serial = serial
0 \ No newline at end of file 26 \ No newline at end of file
cocar/query.py
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 # Inspired by the code in http://www.copyandwaste.com/posts/view/multiprocessing-snmp-with-python/ 3 # Inspired by the code in http://www.copyandwaste.com/posts/view/multiprocessing-snmp-with-python/
4 __author__ = 'eduardo' 4 __author__ = 'eduardo'
5 5
6 -from host import SnmpSession 6 +from session import SnmpSession
7 from multiprocessing import Process, Queue, current_process 7 from multiprocessing import Process, Queue, current_process
8 8
9 9
cocar/session.py 0 → 100644
@@ -0,0 +1,116 @@ @@ -0,0 +1,116 @@
  1 +#!/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +# Inspired by the code in http://www.copyandwaste.com/posts/view/multiprocessing-snmp-with-python/
  4 +__author__ = 'eduardo'
  5 +
  6 +import netsnmp
  7 +import subprocess
  8 +import logging
  9 +from . import Cocar
  10 +
  11 +log = logging.getLogger()
  12 +
  13 +
  14 +class Host(object):
  15 + """
  16 + Creates a host record
  17 + """
  18 +
  19 + def __init__(self,
  20 + hostname=None,
  21 + query=None):
  22 + self.hostname = hostname
  23 + self.query = query
  24 +
  25 +
  26 +class SnmpSession(Cocar):
  27 + """A SNMP Session"""
  28 + def __init__(self,
  29 + oid=".1.3.6.1.2.1.1.1.0",
  30 + iid=None,
  31 + Version=2,
  32 + DestHost="localhost",
  33 + Community="public",
  34 + Verbose=True,
  35 + ):
  36 + Cocar.__init__(self)
  37 + self.oid = oid
  38 + self.Version = Version
  39 + self.DestHost = DestHost
  40 + self.Community = Community
  41 + self.Verbose = Verbose
  42 + self.var = netsnmp.Varbind(oid, iid)
  43 + self.hostrec = Host()
  44 + self.hostrec.hostname = self.DestHost
  45 +
  46 + def query(self):
  47 + """Creates SNMP query
  48 +
  49 + Fills out a Host Object and returns result
  50 + """
  51 + try:
  52 + result = netsnmp.snmpget(self.var,
  53 + Version=self.Version,
  54 + DestHost=self.DestHost,
  55 + Community=self.Community)
  56 + self.hostrec.query = result
  57 + except Exception, err:
  58 + if self.Verbose:
  59 + print err
  60 + self.hostrec.query = None
  61 + finally:
  62 + return self.hostrec
  63 +
  64 +
  65 +class NmapSession(Cocar):
  66 + """
  67 + Realiza busca Nmap num ativo de rede
  68 + Inspirado em https://github.com/c0r3dump3d/pylanos
  69 + """
  70 + def __init__(self,
  71 + host,
  72 + full=False,
  73 + outfile=None
  74 + ):
  75 + """
  76 + Parâmetros obrigatórios
  77 + """
  78 + Cocar.__init__(self)
  79 + self.host = host
  80 + self.full = full
  81 + if outfile is not None:
  82 + self.outfile = outfile
  83 + else:
  84 + self.outfile = self.cocar_data_dir + "/" + str(self.host) + ".xml"
  85 +
  86 + def scan(self):
  87 + """
  88 + Realiza busca Nmap
  89 + :return:
  90 + """
  91 + try:
  92 + if self.full:
  93 + scanv = subprocess.Popen(["nmap",
  94 + "-PR",
  95 + "-sV",
  96 + str(self.host),
  97 + "-oX",
  98 + self.outfile],
  99 + stdout=subprocess.PIPE,
  100 + stderr=subprocess.PIPE).communicate()[0]
  101 + else:
  102 + scanv = subprocess.Popen(["nmap",
  103 + "-PE",
  104 + "-PP",
  105 + "-PS21,22,23,25,80,443,3306,3389,8080",
  106 + "-sV",
  107 + str(self.host),
  108 + "-oX",
  109 + self.outfile],
  110 + stdout=subprocess.PIPE,
  111 + stderr=subprocess.PIPE).communicate()[0]
  112 + except OSError:
  113 + log.error("Install nmap: sudo apt-get install nmap")
  114 + return False
  115 +
  116 + return True
0 \ No newline at end of file 117 \ No newline at end of file
cocar/tests/test_discover.py
@@ -3,7 +3,12 @@ @@ -3,7 +3,12 @@
3 __author__ = 'eduardo' 3 __author__ = 'eduardo'
4 4
5 import unittest 5 import unittest
6 -from ..host import Host, SnmpSession 6 +import os
  7 +import os.path
  8 +from ..session import Host, SnmpSession, NmapSession
  9 +from .. import Cocar
  10 +from ..model import network
  11 +from .. import utils
7 12
8 13
9 class TestDiscover(unittest.TestCase): 14 class TestDiscover(unittest.TestCase):
@@ -15,31 +20,98 @@ class TestDiscover(unittest.TestCase): @@ -15,31 +20,98 @@ class TestDiscover(unittest.TestCase):
15 """ 20 """
16 Parâmetros iniciais 21 Parâmetros iniciais
17 """ 22 """
  23 + self.activeip = '127.0.0.1'
  24 + self.inactiveip = '127.1.1.1'
  25 + self.localhost = '127.0.0.1'
  26 + cocar = Cocar()
  27 + self.data_dir = cocar.cocar_data_dir
  28 +
  29 + local_network = utils.get_local_network()
  30 + self.network = network.Network(
  31 + network_ip=str(local_network.cidr),
  32 + name='Rede de teste'
  33 + )
18 34
19 def test_active(self): 35 def test_active(self):
20 """ 36 """
21 Teste que verifica se o ativo de rede está ativo 37 Teste que verifica se o ativo de rede está ativo
22 """ 38 """
23 - session = SnmpSession() 39 + session = SnmpSession(DestHost=self.activeip)
24 result = session.query() 40 result = session.query()
25 print(result.query[0]) 41 print(result.query[0])
26 self.assertIsNotNone(result.query[0]) 42 self.assertIsNotNone(result.query[0])
27 43
28 def test_inactive(self): 44 def test_inactive(self):
29 """ 45 """
30 - Teste que identifica que um inativo 46 + Teste que identifica que um ativo de rede está inativo
31 """ 47 """
32 - session = SnmpSession(DestHost="192.168.0.201") 48 + session = SnmpSession(DestHost=self.inactiveip)
33 result = session.query() 49 result = session.query()
34 print(result.query[0]) 50 print(result.query[0])
35 self.assertIsNone(result.query[0]) 51 self.assertIsNone(result.query[0])
36 52
37 - def test_identify(self): 53 + def test_scan(self):
  54 + """
  55 + Teste que realiza o scan em todas as informações do ativo
  56 + """
  57 + session = NmapSession(self.localhost)
  58 + result = session.scan()
  59 + assert result
  60 +
  61 + # Tenta achar o arquivo
  62 + outfile = self.data_dir + "/" + self.localhost + ".xml"
  63 + assert (os.path.isfile(outfile))
  64 +
  65 + def test_scan_rede_full(self):
38 """ 66 """
39 - Teste que identifica qual é o ativo 67 + Realiza busca em todos os IP's da rede e grava resultados num arquivo específico
40 """ 68 """
  69 + ip_list = self.network.ip_list()
  70 + i = 0
  71 + for ip in ip_list:
  72 + outfile = self.network.network_dir + "/" + str(ip) + ".xml"
  73 + #print(outfile)
  74 + session = NmapSession(ip, outfile=outfile)
  75 + session.scan()
  76 + i += 1
  77 + if i > 10:
  78 + break
  79 +
  80 + # List all IP's from directory
  81 + onlyfiles = [ f for f in os.listdir(self.network.network_dir) if os.path.isfile(os.path.join(self.network.network_dir, f)) ]
  82 +
  83 + # Funciona se encontrar pelo menos um arquivo
  84 + self.assertGreater(len(onlyfiles), 0)
  85 +
  86 + # Apaga diretório
  87 + os.rmdir(self.network.network_dir)
  88 +
  89 + def test_scan_rede(self):
  90 + """
  91 + Realiza busca rápida em todos os IP's da rede e grava resultados num arquivo específico
  92 + """
  93 + ip_list = self.network.ip_list()
  94 + i = 0
  95 + for ip in ip_list:
  96 + outfile = self.network.network_dir + "/" + str(ip) + ".xml"
  97 + #print(outfile)
  98 + session = NmapSession(ip, outfile=outfile, full=False)
  99 + session.scan()
  100 + i += 1
  101 + if i > 10:
  102 + break
  103 +
  104 + # List all IP's from directory
  105 + onlyfiles = [ f for f in os.listdir(self.network.network_dir) if os.path.isfile(os.path.join(self.network.network_dir, f)) ]
  106 +
  107 + # Funciona se encontrar pelo menos um arquivo
  108 + self.assertGreater(len(onlyfiles), 0)
  109 +
  110 + # Apaga diretório
  111 + os.rmdir(self.network.network_dir)
41 112
42 def tearDown(self): 113 def tearDown(self):
43 """ 114 """
44 Apaga dados inicias 115 Apaga dados inicias
45 - """  
46 \ No newline at end of file 116 \ No newline at end of file
  117 + """
  118 + #os.rmdir(self.data_dir)
47 \ No newline at end of file 119 \ No newline at end of file
cocar/utils.py 0 → 100644
@@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
  1 +#!/bin/env python
  2 +# -*- coding: utf-8 -*-
  3 +__author__ = 'eduardo'
  4 +
  5 +import netifaces
  6 +import netaddr
  7 +import socket
  8 +from pprint import pformat
  9 +
  10 +
  11 +def get_local_network(myiface='eth0'):
  12 + """
  13 + Função que encontra a rede local.
  14 + Fonte: http://stackoverflow.com/questions/3755863/trying-to-use-my-subnet-address-in-python-code
  15 +
  16 + :param myiface: Interface local a ser utilizada na busca
  17 + :return: IPNetwork instance
  18 + """
  19 + ifaces = netifaces.interfaces()
  20 + # => ['lo', 'eth0', 'eth1']
  21 +
  22 + addrs = netifaces.ifaddresses(myiface)
  23 + # {2: [{'addr': '192.168.1.150',
  24 + # 'broadcast': '192.168.1.255',
  25 + # 'netmask': '255.255.255.0'}],
  26 + # 10: [{'addr': 'fe80::21a:4bff:fe54:a246%eth0',
  27 + # 'netmask': 'ffff:ffff:ffff:ffff::'}],
  28 + # 17: [{'addr': '00:1a:4b:54:a2:46', 'broadcast': 'ff:ff:ff:ff:ff:ff'}]}
  29 +
  30 + # Get ipv4 stuff
  31 + ipinfo = addrs[socket.AF_INET][0]
  32 + address = ipinfo['addr']
  33 + netmask = ipinfo['netmask']
  34 +
  35 + # Create ip object and get
  36 + cidr = netaddr.IPNetwork('%s/%s' % (address, netmask))
  37 + # => IPNetwork('192.168.1.150/24')
  38 +
  39 + #network = cidr.network
  40 + # => IPAddress('192.168.1.0')
  41 +
  42 + return cidr
development.ini-dist 0 → 100644
@@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
  1 +[cocar]
  2 +data_dir = /srv/cocar-agente/cocar_data
  3 +
  4 +# Begin logging configuration
  5 +[loggers]
  6 +keys = root, cocar
  7 +
  8 +[handlers]
  9 +keys = console
  10 +
  11 +[formatters]
  12 +keys = generic
  13 +
  14 +[logger_root]
  15 +level = DEBUG
  16 +handlers = console
  17 +
  18 +[logger_cocar]
  19 +level = DEBUG
  20 +handlers =
  21 +qualname = lbgenerator
  22 +
  23 +[handler_console]
  24 +class = StreamHandler
  25 +args = (sys.stderr,)
  26 +level = DEBUG
  27 +formatter = generic
  28 +
  29 +[formatter_generic]
  30 +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
  31 +datefmt = %H:%M:%S
  32 +# End logging configuration
0 \ No newline at end of file 33 \ No newline at end of file
@@ -2,6 +2,10 @@ from distutils.core import setup @@ -2,6 +2,10 @@ from distutils.core import setup
2 2
3 requires = [ 3 requires = [
4 'multiprocessing', 4 'multiprocessing',
  5 + 'python-nmap',
  6 + 'ipy',
  7 + 'netaddr',
  8 + 'netifaces'
5 ] 9 ]
6 10
7 11