From b5705dd55bf42fee6ec2dc177669cdff25eb3b0d Mon Sep 17 00:00:00 2001 From: "thiago.purkote" Date: Mon, 9 Sep 2024 18:16:11 -0300 Subject: [PATCH] =?UTF-8?q?integra=C3=A7=C3=A3o=5Finfluxdb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/json/itguys.json | 85 ++++++---- python/requirements.txt | 10 ++ python/server.py | 359 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 400 insertions(+), 54 deletions(-) create mode 100644 python/requirements.txt diff --git a/python/json/itguys.json b/python/json/itguys.json index 1078848..57c04fd 100644 --- a/python/json/itguys.json +++ b/python/json/itguys.json @@ -1,28 +1,57 @@ -[ - { - "imagem_Cab": "", - "A_cab": "..", - "button_Cab": "Logout", - "imagem_segumento_1": "../Acessts/Imagens/Iconis/seta-direita.png", - "imagem_User_1_segumento_2": "../Acessts/Imagens/Iconis/profile-user.png", - "imagem_User_2_segumento_2": "../Acessts/Imagens/Iconis/profile-user.png", - "texto_User_segumento_2": "Usuario", - "texto_Empresa_segumento_2": "itguys", - "Menu_home_a": "../Ambiente do usuario/Tela_home/Home_page.html", - "Menu_home_img": "../Acessts/Imagens/Iconis/home.png", - "Menu_home_text": "Home", - "Menu_Monitor_a": "./Tela_Monitoramento/Monitoramento copy.html", - "Menu_Monitor_img": "../Acessts/Imagens/Iconis/monitor.png", - "Menu_Monitor_text": "Monitoramento", - "Menu_Servicedesk_a": "https://servicedesk.itguys.com.br/HomePage.do", - "Menu_Servicedesk_img": "../Acessts/Imagens/Iconis/technical-support.png", - "Menu_Servicedesk_text": "Servicedesk", - "Menu_Suporte_a": "https://api.whatsapp.com/send?phone=5521966344698", - "Menu_Suporte_img": "../Acessts/Imagens/Iconis/central-de-atendimento.png", - "Menu_Suporte_text": "Suporte", - "Menu_Config_a": "./Tela_config/Config_Ambiente_usuario.html", - "Menu_Config_img": "../Acessts/Imagens/Iconis/engrenagem - Copia.png", - "Menu_Config_text": "Configuracões", - "Tela": "./Tela_home/Home_page.html" - } -] \ No newline at end of file +{ + "info_html": [ + { + "imagem_Cab": "", + "A_cab": "..", + "button_Cab": "Logout", + "imagem_segumento_1": "../Acessts/Imagens/Iconis/seta-direita.png", + "imagem_User_1_segumento_2": "../Acessts/Imagens/Iconis/profile-user.png", + "imagem_User_2_segumento_2": "../Acessts/Imagens/Iconis/profile-user.png", + "texto_User_segumento_2": "Usuario", + "texto_Empresa_segumento_2": "itguys", + "Menu_home_a": "../Ambiente do usuario/Tela_home/Home_page.html", + "Menu_home_img": "../Acessts/Imagens/Iconis/home.png", + "Menu_home_text": "Home", + "Menu_Monitor_a": "./Tela_Monitoramento/Monitoramento copy.html", + "Menu_Monitor_img": "../Acessts/Imagens/Iconis/monitor.png", + "Menu_Monitor_text": "Monitoramento", + "Menu_Servicedesk_a": "https://servicedesk.itguys.com.br/HomePage.do", + "Menu_Servicedesk_img": "../Acessts/Imagens/Iconis/technical-support.png", + "Menu_Servicedesk_text": "Servicedesk", + "Menu_Suporte_a": "https://api.whatsapp.com/send?phone=5521966344698", + "Menu_Suporte_img": "../Acessts/Imagens/Iconis/central-de-atendimento.png", + "Menu_Suporte_text": "Suporte", + "Menu_Config_a": "./Tela_config/Config_Ambiente_usuario.html", + "Menu_Config_img": "../Acessts/Imagens/Iconis/engrenagem - Copia.png", + "Menu_Config_text": "Configuracões", + "Tela": "./Tela_home/Home_page.html" + } + ], + "config_influxdb": [ + { + "Token": "lLgq7Y2eXZ5Hxc68dkyFULevz5pO1QPjQsk23M0ETe0hWDK8n0Fa2hIfUQxOek8n0IpRcy46U3ioU3C9htr3YA==", + "org": "iTGuys", + "url": "http://influxdb.itguys.com.br" + } + ], + "queries": [ + { + "uid": "b6f3a8d4-01a4-4c53-a2a8-123456789abc", + "query_name": "Server CPU", + "query": "from(bucket: \"${Bucket}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"cpustat\" and\n r._field == \"cpu\"\n )\n |> filter(fn: (r) => r[\"host\"] == \"${server}\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean)", + "description": "Consulta para obter o uso médio de CPU no último 1 hora." + }, + { + "uid": "a2b9d8e5-77b4-4f9d-a2b8-abcdef123456", + "query_name": "Logical Cores", + "query": "from(bucket: \"${Bucket}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"cpustat\" and\n r._field == \"cpus\"\n )\n |> filter(fn: (r) => r[\"host\"] == \"${server}\")\n |> limit(n:1)", + "description": "Consulta para obter o uso médio de memória no último 1 hora." + }, + { + "uid": "8e214351-69ca-4160-a72f-17d46e2231ea", + "query_name": "Total Memory", + "query": "from(bucket: \"${Bucket}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"memory\" and\n r._field == \"memtotal\"\n )\n |> filter(fn: (r) => r[\"host\"] == \"${server}\")\n |> limit(n:1)", + "description": "Consulta para obter a média de I/O de disco nos últimos 30 minutos." + } + ] +} \ No newline at end of file diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 0000000..fdb9dbb --- /dev/null +++ b/python/requirements.txt @@ -0,0 +1,10 @@ +Flask==2.3.2 # Framework para criar APIs +Flask-MySQLdb==0.2.0 # Integração com MySQL +ldap3==2.9 # Biblioteca para autenticação com servidores LDAP +python-dotenv==1.0.0 # Carregar variáveis de ambiente de arquivos .env +PyJWT==2.6.0 # Para gerar e decodificar tokens JWT +Flask-Cors==3.0.10 # Suporte a CORS (Cross-Origin Resource Sharing) +Flask-WTF==1.1.1 # Proteção contra CSRF (Cross-Site Request Forgery) +Flask-Talisman==0.8.1 # Para melhorar a segurança da aplicação +influxdb-client==1.35.0 # Cliente para InfluxDB +gunicorn==20.1.0 # Servidor WSGI (opcional, caso deseje usar em produção) diff --git a/python/server.py b/python/server.py index 1e404b8..0d50682 100644 --- a/python/server.py +++ b/python/server.py @@ -3,6 +3,9 @@ from flask_mysqldb import MySQL import ldap3 import re import os +import json +from functools import wraps +from collections import OrderedDict from dotenv import load_dotenv import jwt # Importa a biblioteca JWT para gerar e validar tokens from datetime import datetime, timedelta @@ -10,6 +13,14 @@ from ldap3.core.exceptions import LDAPSocketOpenError, LDAPException # Importa from flask_cors import CORS from flask_wtf.csrf import CSRFProtect # Importa CSRFProtect para proteção CSRF from flask_talisman import Talisman +import influxdb_client, time +from influxdb_client import InfluxDBClient, Point, WritePrecision +from influxdb_client.client.write_api import SYNCHRONOUS +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend +import ldap3 # Carregar variáveis de ambiente do arquivo .env load_dotenv() @@ -44,6 +55,25 @@ app.config['SMTP_RECIPIENT'] = os.getenv('SMTP_RECIPIENT') mysql = MySQL(app) +# Função para derivar a chave usando PBKDF2 +def derive_key(password: bytes, salt: bytes): + kdf = PBKDF2HMAC( + algorithm=hashes.SHA512(), + length=32, # O tamanho da chave de AES-256 é 32 bytes + salt=salt, + iterations=500000, + backend=default_backend() + ) + key = kdf.derive(password) + return key + +# Função para descriptografar dados +def decrypt_aes_cbc(ciphertext, key, iv): + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) + decryptor = cipher.decryptor() + plaintext = decryptor.update(ciphertext) + decryptor.finalize() + return plaintext + def recebe_smtp_variaveis_env(): return { 'SMTP_USERNAME': app.config['SMTP_USERNAME'], @@ -53,6 +83,60 @@ def recebe_smtp_variaveis_env(): 'SMTP_RECIPIENT': app.config['SMTP_RECIPIENT'] } +def token_required(f): + @wraps(f) # Adiciona wraps aqui para manter o nome da função original + def wrapper(*args, **kwargs): + token = request.headers.get('x-access-token') + if not token: + return jsonify({'msg': 'Token é necessário!'}), 401 + + try: + # Decodifica o token para validar + data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"]) + except jwt.ExpiredSignatureError: + # O token expirou + return jsonify({'msg': 'Token expirado! Por favor, faça login novamente.'}), 401 + except jwt.InvalidTokenError: + # Token inválido por qualquer outro motivo + return jsonify({'msg': 'Token inválido!'}), 401 + + return f(*args, **kwargs) + return wrapper + +def get_influxdb_config(domain, mysql): + # Consultar o banco de dados para obter o nome da empresa com base no domínio + cur = mysql.connection.cursor() + cur.execute("SELECT nome FROM empresa WHERE dominio = %s", (domain,)) + empresa_result = cur.fetchone() + cur.close() + + # Verifica se a empresa foi encontrada + if empresa_result is None: + raise ValueError('Empresa não encontrada no banco de dados') + + # Obtém o nome da empresa (empresa_result é uma tupla) + nome_empresa = empresa_result[0] + + # Definir o caminho do diretório onde os arquivos JSON estão armazenados + json_directory = './json/' # Substitua pelo caminho real do diretório + + # Nome do arquivo JSON correspondente ao nome da empresa + json_filename = f"{nome_empresa}.json" + + # Caminho completo para o arquivo JSON + json_filepath = os.path.join(json_directory, json_filename) + + # Verificar se o arquivo JSON existe + if not os.path.isfile(json_filepath): + raise FileNotFoundError('Arquivo JSON não encontrado para a empresa fornecida') + + # Carregar o conteúdo do arquivo JSON mantendo a ordem + with open(json_filepath, 'r') as json_file: + json_data = json.load(json_file, object_pairs_hook=OrderedDict) + + return json_data # Retorna todo o JSON carregado + + @app.route('/envio-smtp', methods=['GET']) def envio_smtp(): smtp_variaveis_env = recebe_smtp_variaveis_env() @@ -60,12 +144,12 @@ def envio_smtp(): @app.route('/login', methods=['POST']) @csrf.exempt # Endpoints de autenticação como este podem ser excluídos da proteção CSRF, pois o token CSRF não está disponível antes do login; remova este decorador se quiser aplicar proteção CSRF. - def login(): data = request.get_json() username_full = data.get('username') # Extrai o e-mail completo do usuário dos dados da requisição. - password = data.get('password') # Extrai a senha dos dados da requisição. - + password = data.get('criptpassaword') # Extrai a senha dos dados da requisição. + print(username_full) + print(data.get('criptpassaword')) # Validação do formato do e-mail if not re.match(r"[^@]+@[^@]+\.[^@]+", username_full): return jsonify({'msg': 'Formato de e-mail inválido'}), 400 @@ -123,28 +207,9 @@ def login(): # Captura qualquer outra exceção LDAP genérica return jsonify({'Erro 500': f'Erro LDAP: {str(e)}'}), 500 -# Decorador para proteger rotas com JWT -def token_required(f): - def wrapper(*args, **kwargs): - token = request.headers.get('x-access-token') - if not token: - return jsonify({'msg': 'Token é necessário!'}), 401 - - try: - # Decodifica o token para validar - data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"]) - except jwt.ExpiredSignatureError: - # O token expirou - return jsonify({'msg': 'Token expirado! Por favor, faça login novamente.'}), 401 - except jwt.InvalidTokenError: - # Token inválido por qualquer outro motivo - return jsonify({'msg': 'Token inválido!'}), 401 - - return f(*args, **kwargs) - return wrapper @app.route('/mounting', methods=['GET']) -@token_required # Garante que o usuário esteja autenticado e o token JWT seja validado +@token_required def mounting(): # Decodifica o token para obter o e-mail do usuário token = request.headers.get('x-access-token') @@ -154,6 +219,7 @@ def mounting(): # Extrair o domínio do email do usuário autenticado domain = username_full.split('@')[1] + # Verifica se o usuário está autenticado if not username_full: return jsonify({'msg': 'Token inválido!'}), 401 @@ -163,6 +229,7 @@ def mounting(): empresa_result = cur.fetchone() cur.close() + # Verifica se a empresa foi encontrada if empresa_result is None: return jsonify({'msg': 'Empresa não encontrada no banco de dados para o domínio fornecido'}), 404 @@ -182,8 +249,248 @@ def mounting(): if not os.path.isfile(json_filepath): return jsonify({'msg': 'Arquivo JSON não encontrado para a empresa fornecida'}), 404 - # Retornar o conteúdo do arquivo JSON - return send_from_directory(json_directory, json_filename, as_attachment=False) + # Carregar o conteúdo do arquivo JSON mantendo a ordem + with open(json_filepath, 'r') as json_file: + json_data = json.load(json_file, object_pairs_hook=OrderedDict) + + # Extrair apenas o conteúdo de 'info_html' + info_html_content = json_data.get('info_html', {}) + + # Retornar a resposta JSON com apenas o conteúdo de 'info_html' + return jsonify(info_html_content) + +@app.route('/api/options', methods=['GET']) +@token_required +@csrf.exempt +def get_options(): + client = None + try: + # Decodifica o token para obter o e-mail do usuário + token = request.headers.get('x-access-token') + data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"]) + username_full = data['user'] + + # Extrair o domínio do email do usuário autenticado + domain = username_full.split('@')[1] + + # Carregar as configurações do InfluxDB a partir do JSON + json_data = get_influxdb_config(domain, mysql) + + # Extrair o token de 'config_influxdb' + config_influxdb = json_data.get('config_influxdb', []) + + if not config_influxdb or not isinstance(config_influxdb, list): + return jsonify({'msg': 'config_influxdb não encontrado ou inválido'}), 404 + + # Acessa o primeiro objeto da lista e obtém os detalhes necessários + influxdb_token = config_influxdb[0].get('Token', '') + influxdb_org = config_influxdb[0].get('org', '') + influxdb_url = config_influxdb[0].get('url', '') + + # Verifica se todos os dados necessários foram encontrados + if not influxdb_token: + return jsonify({'msg': 'Token não encontrado em config_influxdb'}), 404 + if not influxdb_org: + return jsonify({'msg': 'Org não encontrado em config_influxdb'}), 404 + if not influxdb_url: + return jsonify({'msg': 'URL não encontrado em config_influxdb'}), 404 + + # Inicializa o cliente InfluxDB + client = influxdb_client.InfluxDBClient(url=influxdb_url, token=influxdb_token, org=influxdb_org) + query_api = client.query_api() + + # Consulta para listar os buckets disponíveis + buckets_query = ''' + buckets() + ''' + buckets_result = query_api.query(org=influxdb_org, query=buckets_query) + + # Processa a lista de buckets + buckets = [] + for table in buckets_result: + for record in table.records: + bucket_name = record.values.get('name') # Acessa diretamente o nome do bucket + if bucket_name: + buckets.append(bucket_name) + + if len(buckets) == 0: + return jsonify({'msg': 'Nenhum bucket encontrado'}), 404 + + # Para cada bucket, listar os nodenames diretamente + nodenames = {} + for bucket in buckets: + nodenames_query = f""" + from(bucket: \"{bucket}\") + |> range(start: -1d) + |> keep(columns: [\"nodename\"]) + |> distinct(column: \"nodename\") + """ + nodenames_result = query_api.query(org=influxdb_org, query=nodenames_query) + + bucket_nodenames = [] + for table in nodenames_result: + for record in table.records: + if record.get_value() is not None: # Ignorar valores nulos + bucket_nodenames.append(record.get_value()) + + nodenames[bucket] = bucket_nodenames + + # Retorna a lista de buckets e nodenames como resposta JSON + return jsonify({ + "buckets": buckets, + "nodenames": nodenames + }) + + except Exception as e: + return jsonify({'msg': 'Erro ao recuperar opções', 'error': str(e)}), 500 + + finally: + if client is not None: + client.close() + +@app.route('/api/execute_all_queries', methods=['POST']) +@token_required +@csrf.exempt +def execute_all_queries(): + client = None + try: + # Decodifica o token para obter o e-mail do usuário + token = request.headers.get('x-access-token') + data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"]) + username_full = data['user'] + domain = username_full.split('@')[1] + + # Carregar as configurações do InfluxDB e queries a partir do JSON + json_data = get_influxdb_config(domain, mysql) + + # Extrair as informações da requisição + request_data = request.get_json() + bucket = request_data.get('bucket') + host = request_data.get('host') + start_time = request_data.get('startTime') + end_time = request_data.get('endTime') + window_period = request_data.get('windowPeriod') + + # Inicializa o cliente InfluxDB + influxdb_token = json_data['config_influxdb'][0]['Token'] + influxdb_org = json_data['config_influxdb'][0]['org'] + influxdb_url = json_data['config_influxdb'][0]['url'] + client = influxdb_client.InfluxDBClient(url=influxdb_url, token=influxdb_token, org=influxdb_org) + query_api = client.query_api() + + # Lista para armazenar os resultados de cada query + all_results = [] + + # Itera sobre todas as queries no JSON + for query_data in json_data['queries']: + # Substitui as variáveis na query + query_template = query_data['query'] + query = query_template.replace("${Bucket}", bucket).replace("${server}", host) + query = query.replace("v.timeRangeStart", start_time).replace("v.timeRangeStop", end_time) + query = query.replace("v.windowPeriod", window_period) + + # Executa a query + result = query_api.query(org=influxdb_org, query=query) + + # Processa os resultados da query + output = [] + for table in result: + for record in table.records: + output.append({ + "time": record.get_time(), + "value": record.get_value(), + "measurement": record.get_measurement(), + "tags": record.values # Inclui as tags relacionadas ao registro + }) + + # Adiciona os resultados processados à lista de todas as queries + all_results.append({ + "uid": query_data['uid'], + "name": query_data['query_name'], + "result": output + }) + + # Retorna o resultado de todas as queries + return jsonify(all_results) + + except Exception as e: + return jsonify({'msg': 'Erro ao executar as queries', 'error': str(e)}), 500 + + finally: + if client: + client.close() + +@app.route('/api/execute_query/', methods=['POST']) +@token_required +@csrf.exempt +def execute_query(uid): + client = None + try: + # Decodifica o token para obter o e-mail do usuário + token = request.headers.get('x-access-token') + data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"]) + username_full = data['user'] + domain = username_full.split('@')[1] + + # Carregar as configurações de InfluxDB e queries a partir do JSON + json_data = get_influxdb_config(domain, mysql) + + # Extrair as informações da requisição + request_data = request.get_json() + bucket = request_data.get('bucket') + host = request_data.get('host') + start_time = request_data.get('startTime') + end_time = request_data.get('endTime') + window_period = request_data.get('windowPeriod') + + # Encontra a query específica com o UID fornecido + query_data = next((q for q in json_data['queries'] if q['uid'] == uid), None) + + if not query_data: + return jsonify({'msg': 'Consulta não encontrada'}), 404 + + # Substitui as variáveis na query + query_template = query_data['query'] + query = query_template.replace("${Bucket}", bucket).replace("${server}", host) + query = query.replace("v.timeRangeStart", start_time).replace("v.timeRangeStop", end_time) + query = query.replace("v.windowPeriod", window_period) + + # Inicializa o cliente InfluxDB + influxdb_token = json_data['config_influxdb'][0]['Token'] + influxdb_org = json_data['config_influxdb'][0]['org'] + influxdb_url = json_data['config_influxdb'][0]['url'] + client = influxdb_client.InfluxDBClient(url=influxdb_url, token=influxdb_token, org=influxdb_org) + query_api = client.query_api() + + # Executa a query + result = query_api.query(org=influxdb_org, query=query) + + # Processa os resultados da query + output = [] + for table in result: + for record in table.records: + output.append({ + "time": record.get_time(), + "value": record.get_value(), + "measurement": record.get_measurement(), + "tags": record.values # Inclui as tags relacionadas ao registro + }) + + # Retorna o resultado da consulta específica + return jsonify({ + "uid": query_data['uid'], + "name": query_data['query_name'], + "result": output + }) + + except Exception as e: + return jsonify({'msg': 'Erro ao executar a query', 'error': str(e)}), 500 + + finally: + if client: + client.close() + + if __name__ == '__main__': - app.run(host="0.0.0.0", port=5000, ssl_context=('./fullchain1.pem','./privkey1.pem')) + app.run(host="0.0.0.0", port=5000, ssl_context=('./fullchain1.pem', './privkey1.pem'))