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

.gitignore
... ... @@ -43,3 +43,7 @@ coverage.xml
43 43 docs/_build/
44 44 # PyBuilder
45 45 target/
  46 +
  47 +# Pycharm
  48 +.idea
  49 +*.ini
... ...
README.md
... ... @@ -9,4 +9,15 @@ Para funcionar é necessário primeiro instalar o pacote da distribuição e só
9 9  
10 10 <pre>
11 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 23 </pre>
13 24 \ No newline at end of file
... ...
cocar/__init__.py
  1 +#!/bin/env python
  2 +# -*- coding: utf-8 -*-
1 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 34 \ No newline at end of file
... ...
cocar/host.py
... ... @@ -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 0 \ No newline at end of file
cocar/model/__init__.py 0 → 100644
... ... @@ -0,0 +1 @@
  1 +__author__ = 'eduardo'
... ...
cocar/model/computer.py 0 → 100644
... ... @@ -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 21 \ No newline at end of file
... ...
cocar/model/host.py 0 → 100644
... ... @@ -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 38 \ No newline at end of file
... ...
cocar/model/network.py 0 → 100644
... ... @@ -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 46 \ No newline at end of file
... ...
cocar/model/printer.py 0 → 100644
... ... @@ -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 26 \ No newline at end of file
... ...
cocar/query.py
... ... @@ -3,7 +3,7 @@
3 3 # Inspired by the code in http://www.copyandwaste.com/posts/view/multiprocessing-snmp-with-python/
4 4 __author__ = 'eduardo'
5 5  
6   -from host import SnmpSession
  6 +from session import SnmpSession
7 7 from multiprocessing import Process, Queue, current_process
8 8  
9 9  
... ...
cocar/session.py 0 → 100644
... ... @@ -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 117 \ No newline at end of file
... ...
cocar/tests/test_discover.py
... ... @@ -3,7 +3,12 @@
3 3 __author__ = 'eduardo'
4 4  
5 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 14 class TestDiscover(unittest.TestCase):
... ... @@ -15,31 +20,98 @@ class TestDiscover(unittest.TestCase):
15 20 """
16 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 35 def test_active(self):
20 36 """
21 37 Teste que verifica se o ativo de rede está ativo
22 38 """
23   - session = SnmpSession()
  39 + session = SnmpSession(DestHost=self.activeip)
24 40 result = session.query()
25 41 print(result.query[0])
26 42 self.assertIsNotNone(result.query[0])
27 43  
28 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 49 result = session.query()
34 50 print(result.query[0])
35 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 113 def tearDown(self):
43 114 """
44 115 Apaga dados inicias
45   - """
46 116 \ No newline at end of file
  117 + """
  118 + #os.rmdir(self.data_dir)
47 119 \ No newline at end of file
... ...
cocar/utils.py 0 → 100644
... ... @@ -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 @@
  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 33 \ No newline at end of file
... ...
setup.py
... ... @@ -2,6 +2,10 @@ from distutils.core import setup
2 2  
3 3 requires = [
4 4 'multiprocessing',
  5 + 'python-nmap',
  6 + 'ipy',
  7 + 'netaddr',
  8 + 'netifaces'
5 9 ]
6 10  
7 11  
... ...