Commit 45d477ecea855227b17ea3b81b94bd5f2b456b06
Committed by
Eduardo Santos
Exists in
master
Primeira versão do módulo capaz de realizar a descoberta da rede
Showing
15 changed files
with
449 additions
and
65 deletions
Show diff stats
.gitignore
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 |
... | ... | @@ -0,0 +1 @@ |
1 | +__author__ = 'eduardo' | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 nó 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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |