【原创】全自动添加防火墙vpn+JumpServer账号

使用wxbot对接华三防火墙开通vpn账号和开通JumpServer堡垒机账号,让运维如此轻松

效果


管理员激活

申请vpn和堡垒机账号指令 /申请vpn+公司邮箱地址+访问使用说明
/申请vpn 362@qq.com test

激活指令 /激活vpn+任务id+权限id
/激活vpn e432abeb-e5b5-47f9-a4c7-a7e86b79f5f4 b6c715c5-d997-4017-a265-56d37e4a3acf

密码通过邮件发送

上代码

请配置好防火墙管理员账号和密码 、JumpServer管理员账号的ak sk

# -*- coding:utf-8 -*-
import http.client
import ssl  # 导入证书模块
import json
import sys
import uuid
import datetime
import websocket
import time
import random
import json
import requests
import paramiko
import os
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
import re
import uuid
import mysql.connector
import string
import logging
from httpsig.requests_auth import HTTPSignatureAuth
from bs4 import BeautifulSoup
from mysql.connector import pooling
from datetime import datetime
from httpsig.requests_auth import HTTPSignatureAuth
from datetime import datetime
from email.utils import formatdate
# 设置日志记录到文件和控制台
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                    handlers=[
                        logging.FileHandler("app.log"),
                        logging.StreamHandler(sys.stdout)  # 输出到控制台
                    ])
#################################申请vpn和堡垒机 和激活功能

def send_email(to_email, vpn_account, vpn_password):
    smtp_server = 'smtp.exmail.qq.com'
    smtp_port = 465
    from_email = 'f******_admin@fzda********ke.com'
    from_password = '***************************oq'  # 请确保在生产环境中妥善保管密码

    # 构建邮件内容
    subject = "root - Your VPN Account Details"
    body = f"Dear user,\n\n您的VPN和堡垒机帐户 '{vpn_account}' has been created successfully. Your password is: {vpn_password}\n\n请及时修改密码。\n\n致以最诚挚的问候,\nroot-交付运维中心\n【腾讯文档】互联网区域堡垒机使用方法v20210713https://docs.qq.com/doc/DWm*************ek1W"
    
    # 设置邮件的 MIME 类型和内容
    message = MIMEMultipart()
    message['From'] = Header("root-IT Support", 'utf-8')
    message['To'] = Header(to_email, 'utf-8')
    message['Subject'] = Header(subject, 'utf-8')
    
    message.attach(MIMEText(body, 'plain', 'utf-8'))
    
    try:
        # 使用 SSL 连接到 SMTP 服务器
        with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
            server.login(from_email, from_password)
            server.sendmail(from_email, to_email, message.as_string())
        
        logging.info(f"Email sent successfully to {to_email}")

    except smtplib.SMTPException as e:
        logging.error(f"Failed to send email to {to_email}: {str(e)}")
        
        
def generate_strong_password(length=12):
    # 确保密码包含至少一个大写字母、小写字母、数字和特殊字符
    password_characters = string.ascii_letters + string.digits + string.punctuation

    # 随机选择一个大写字母、小写字母、数字和特殊字符
    password = [
        random.choice(string.ascii_uppercase),
        random.choice(string.ascii_lowercase),
        random.choice(string.digits),
        random.choice(string.punctuation),
    ]

    # 填充剩余的密码长度
    password += [random.choice(password_characters) for _ in range(length - 4)]

    # 打乱密码字符的顺序
    random.shuffle(password)

    return ''.join(password)

def apply_vpn_account(applicant_wxid, email, purpose):
    vpn_account = email.split('@')[0]
    vpn_password = generate_strong_password()
    application_uuid = str(uuid.uuid4())
    logging.info(f"Info: apply_vpn_account {vpn_account} with vpn_password {vpn_password} application_uuid {application_uuid}")

    # 连接MySQL数据库并保存申请记录
    connection = mysql.connector.connect(
        host="172.2.2.50",
        user="root",
        password="root",
        database="root"
    )
    cursor = connection.cursor()

    # 检查邮箱是否已经存在
    check_query = "SELECT COUNT(*) FROM fa_vpn_fw_blj WHERE email = %s"
    cursor.execute(check_query, (email,))
    (count,) = cursor.fetchone()

    if count > 0:
        logging.warning(f"Warning: Email {email} 申请过.")
        cursor.close()
        connection.close()
        return None, None, None

    # 插入新记录
    query = ("INSERT INTO fa_vpn_fw_blj (uuid, applicant_wxid, vpn_account, vpn_password, email, purpose, status) "
             "VALUES (%s, %s, %s, %s, %s, %s, %s)")
    cursor.execute(query, (application_uuid, applicant_wxid, vpn_account, vpn_password, email, purpose, '待激活'))
    connection.commit()
    
    cursor.close()
    connection.close()
    
    return application_uuid, vpn_account, vpn_password
    
def get_auth(KeyID, SecretID):
    signature_headers = ['(request-target)', 'accept', 'date']
    auth = HTTPSignatureAuth(key_id=KeyID, secret=SecretID, algorithm='hmac-sha256', headers=signature_headers)
    return auth

def get_users_groups():
    try:
        # 调用堡垒机API接口查询权限组
        url = "https://172.20.40.27/api/v1/users/groups/"
        
        # 获取当前的日期时间
        date_header = formatdate(timeval=None, localtime=False, usegmt=True)
        
        headers = {
            "Content-Type": "application/json",
            "Authorization": "Bearer your_token",
            "date": date_header
        }

        auth = get_auth("361e490f-***************-99482dc6113f", "029b1e59-aa10-*************-131a3b390ef5")
        response = requests.get(url, headers=headers, auth=auth, verify=False)
        
        logging.info(f"Response status code: {response.status_code}")
        if response.status_code == 200:
            groups = response.json()
            logging.info(f"查询堡垒机权限组成功: {groups}")
            return groups
        else:
            logging.error(f"查询堡垒机权限组失败: {response.text}")
            return None
            
    except Exception as e:
        logging.error(f"Failed to query user groups from the bastion host: {str(e)}")
        raise
def combine_id_name_to_string(groups):
    # 将每个 id 和 name 组合成一个小字符串
    id_name_strings = [f"ID: {group['id']}, Name: {group['name']}" for group in groups]
    
    # 将所有小字符串组合成一个长字符串,使用换行符分隔
    combined_string = "\n".join(id_name_strings)
    
    return combined_string

def activate_vpn_account(application_uuid, users_groupsID , activator_wxid):
    try:
        # 连接MySQL数据库并获取申请记录
        connection = mysql.connector.connect(
            host="172.2.2.50",
            user="root",
            password="root",
            database="root"
        )
        cursor = connection.cursor()
        
        query = ("SELECT vpn_account, vpn_password, email FROM fa_vpn_fw_blj WHERE uuid=%s AND status='待激活'")
        cursor.execute(query, (application_uuid,))
        result = cursor.fetchone()
        
        if result:
            vpn_account, vpn_password, email = result
            logging.info(f"Activating VPN account for {vpn_account} with email {email} by {activator_wxid}")

            try:
                # 连接防火墙,执行VPN账号添加功能
                ssh = paramiko.SSHClient()
                ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                ssh.connect("172.50.0.1", port=22, username="*****************", password="****************")
                
                cmd = f"""
                sys
                user-group psy_system_auto_add
                identity-member user {vpn_account}
                exit
                local-user {vpn_account} class network
                password simple {vpn_password}
                access-limit 1
                service-type sslvpn
                group psy_system_auto_add
                authorization-attribute user-role network-operator
                authorization-attribute sslvpn-policy-group SSLVPNZIYUA
                identity-group psy_system_auto_add
                description {vpn_account}-{email}
                exit
                save
                y
                """
                stdin, stdout, stderr = ssh.exec_command(cmd)
                stdout.channel.recv_exit_status()  # 确保命令执行完成
                ssh.close()
                logging.info(f"VPN account {vpn_account} added to the firewall successfully.")
            
            except Exception as e:
                logging.error(f"Failed to add VPN account to the firewall: {str(e)}")
                return(f"Failed to add VPN account to the firewall: {str(e)}")
                raise  # 重新抛出异常,以便进一步处理
            logging.info(f"堡垒机账号 {vpn_account} 密码{vpn_password}。")
            try:
                # 调用堡垒机API接口添加账号
                url = "https://172.20.40.27/api/v1/users/users/"
                
                # 获取当前的日期时间
                date_header = formatdate(timeval=None, localtime=False, usegmt=True)
                
                headers = {
                    "Content-Type": "application/json",
                    "Authorization": "Bearer your_token",
                    "date": date_header
                }
                data = {
                    "id": application_uuid,
                    "name": vpn_account,
                    "username": vpn_account,
                    "password": vpn_password,
                    "email": email,
                    "role": "User",
                    "groups": [f"{users_groupsID}"],
                    "password_strategy": "custom"
                }
                auth = get_auth("361e490f-************-99482dc6113f", "029b1e59-aa10-********************1a3b390ef5")
                response = requests.post(url, headers=headers, json=data, auth=auth ,verify=False)
                
                if response.status_code == 201:
                    # 更新数据库状态为已激活,记录激活操作人wxid和激活时间
                    activation_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                    update_query = """
                    UPDATE fa_vpn_fw_blj 
                    SET status=%s, activator_wxid=%s, activation_time=%s 
                    WHERE uuid=%s
                    """
                    cursor.execute(update_query, ('已激活', activator_wxid, activation_time, application_uuid))
                    connection.commit()
                    logging.info(f"堡垒机账号 {vpn_account} 添加成功,并已激活。")
                    send_email(email, vpn_account, vpn_password) 
                    return(f"堡垒机账号 {vpn_account} 添加成功,并已激活。账号和密码信息请查收由fzdatalake_admin@fzdatalake.com发送的邮件信息")
                else:
                    logging.error(f"堡垒机账号添加失败: {response.text}")
                    return(f"堡垒机账号添加失败: {response.text}")
            
            except Exception as e:
                logging.error(f"Failed to add account to the bastion host: {str(e)}")
                raise  # 重新抛出异常,以便进一步处理
        
        else:
            logging.warning(f"没有待定的VPN激活 for UUID: {application_uuid}")
            return(f"没有待定的VPN激活/或已经激活过 for UUID: {application_uuid}")
    
    except mysql.connector.Error as err:
        logging.error(f"Database error: {str(err)}")
    
    except Exception as e:
        logging.error(f"An error occurred: {str(e)}")
    
    finally:
        if cursor:
            cursor.close()
        if connection:
            connection.close()

#################################申请vpn和堡垒机 和激活功能
def output(msg):
    now = time.strftime("%Y-%m-%d %X")
    logging.info(f'[{now}]: {msg} - {script_name}')

# 获取当前脚本的名称  
script_name = os.path.basename(__file__)  
output(f"当前脚本的名称是: {script_name}")

ip = '172.2.2.50'
port = 5555

SERVER = f'ws://{ip}:{port}'
HEART_BEAT = 5005
RECV_TXT_MSG = 1
RECV_TXT_CITE_MSG = 49
RECV_PIC_MSG = 3
USER_LIST = 5000
GET_USER_LIST_SUCCSESS = 5001
GET_USER_LIST_FAIL = 5002
TXT_MSG = 555
PIC_MSG = 500
AT_MSG = 550
CHATROOM_MEMBER = 5010
CHATROOM_MEMBER_NICK = 5020
PERSONAL_INFO = 6500
DEBUG_SWITCH = 6000
PERSONAL_DETAIL = 6550
DESTROY_ALL = 9999
JOIN_ROOM = 10000

 

########################################################################################

def match_keyword(keywords, text):
    for keyword in keywords.keys():
        if keyword in text:
            output(keyword)
            return keyword
    return None

def getid():
    return time.strftime("%Y%m%d%H%M%S")

################################### HTTP ################################################
def send(uri, data):
    base_data = {
        'id': getid(),
        'type': 'null',
        'roomid': 'null',
        'wxid': 'null',
        'content': 'null',
        'nickname': 'null',
        'ext': 'null',
    }
    base_data.update(data)
    url = f'http://{ip}:{port}/{uri}'
    try:
        res = requests.post(url, json={'para': base_data}, timeout=5)
        res.raise_for_status()
        return res.json()
    except requests.exceptions.RequestException as e:
        output(f"HTTP Request failed: {e}")
        return None

def get_member_nick(roomid, wxid):
    uri = 'api/getmembernick'
    data = {
        'type': CHATROOM_MEMBER_NICK,
        'wxid': wxid,
        'roomid': roomid or 'null'
    }
    respJson = send(uri, data)
    if respJson:
        return json.loads(respJson['content'])['nick']
    return None

def get_personal_info():
    uri = '/api/get_personal_info'
    data = {
        'id': getid(),
        'type': PERSONAL_INFO,
        'content': 'op:personal info',
        'wxid': 'null',
    }
    respJson = send(uri, data)
    output(respJson)

################################### websocket ################################################
def get_chat_nick_p(roomid):
    qs = {
        'id': getid(),
        'type': CHATROOM_MEMBER_NICK,
        'content': roomid,
        'wxid': 'ROOT',
    }
    return json.dumps(qs)

def debug_switch():
    qs = {
        'id': getid(),
        'type': DEBUG_SWITCH,
        'content': 'off',
        'wxid': 'ROOT',
    }
    return json.dumps(qs)

def handle_nick(j):
    data = j.content
    for d in data:
        output(f'nickname: {d.nickname}')

def hanle_memberlist(j):
    data = j.content
    for d in data:
        output(f'roomid: {d.roomid}')

def get_chatroom_memberlist():
    qs = {
        'id': getid(),
        'type': CHATROOM_MEMBER,
        'wxid': 'null',
        'content': 'op:list member',
    }
    return json.dumps(qs)

def get_personal_detail(wxid):
    qs = {
        'id': getid(),
        'type': PERSONAL_DETAIL,
        'content': 'op:personal detail',
        'wxid': wxid,
    }
    return json.dumps(qs)

def send_wxuser_list():
    qs = {
        'id': getid(),
        'type': USER_LIST,
        'content': 'user list',
        'wxid': 'null',
    }
    return json.dumps(qs)

def handle_wxuser_list(j):
    output('启动完成')

###################################################################################
def heartbeat(msgJson):
    output(msgJson['content'])

def on_open(ws):
    ws.send(send_wxuser_list())

def on_error(ws, error):
    output(f'on_error: {error}')

def on_close(ws):
    output("closed")

###################################################################################
def destroy_all():
    qs = {
        'id': getid(),
        'type': DESTROY_ALL,
        'content': 'none',
        'wxid': 'node',
    }
    return json.dumps(qs)

def send_msg(msg, wxid='null', roomid=None, nickname='null'):
    if msg.endswith('png'):
        msg_type = PIC_MSG
        if roomid:
            wxid = roomid
            roomid = None
            nickname = 'null'
    elif roomid:
        msg_type = AT_MSG
    else:
        msg_type = TXT_MSG
    if roomid is None:
        roomid = 'null'
    qs = {
        'id': getid(),
        'type': msg_type,
        'roomid': roomid,
        'wxid': wxid,
        'content': msg,
        'nickname': nickname,
        'ext': 'null'
    }
    output(f'发送消息: {qs}')
    return json.dumps(qs)

###################################################################################
def welcome_join(msgJson):
    output(f'收到消息: {msgJson}')
    if '邀请' in msgJson['content']['content']:
        roomid = msgJson['content']['id1']
        nickname = msgJson['content']['content'].split('"')[-2]
        #ws.send(send_msg(f'欢迎新进群', roomid=roomid, wxid='null', nickname=nickname))

def handleMsg_cite(msgJson):
    msgXml = msgJson['content']['content'].replace('&amp;', '&').replace('&lt;', '<').replace('&gt;', '>')
    soup = BeautifulSoup(msgXml, 'lxml')
    msgJson = {
        'content': soup.select_one('title').text,
        'id': msgJson['id'],
        'id1': msgJson['content']['id2'],
        'id2': 'wxid_fys2fico9put22',
        'id3': '',
        'srvid': msgJson['srvid'],
        'time': msgJson['time'],
        'type': msgJson['type'],
        'wxid': msgJson['content']['id1']
    }
    handle_recv_msg(msgJson)



def get_type_ollama(operation_content):
    url = "http://192.168.250.249:11434/api/generate"
    payload = json.dumps({
        "model": "qwen2:7b",
        "stream": False,
        "system": "你是一个运维事件记录员,你的名字叫小湖,你只要识别出事件的类型。服务器、应用程序、交换机、防火墙策略、其他这5种类型,选择其中一个简短明了回答是什么类型",
        "prompt": f"{operation_content} ,这个操作事件内容是'服务器、应用程序、交换机、防火墙策略、其他'中是哪种操作?直接回答类型"
    })
    headers = {
        'Content-Type': 'application/json'
    }

    retries = 3
    for attempt in range(retries):
        try:
            response = requests.post(url, headers=headers, data=payload)
            response.raise_for_status()
            result = response.json()
            output(f"识别的类型: {result['response']}")
            return result['response']
        except requests.exceptions.RequestException as e:
            output(f"AI请求失败 (尝试 {attempt + 1}/{retries}): {e}")
            time.sleep(2)
    output("AI服务不可用,使用默认类型")
    return "其他"  # 默认类型或错误处理

def handle_recv_msg(msgJson):
    logging.info(f'Received message: {msgJson}')

    # 获取消息内容
    keyword = msgJson['content'].replace('\u2005', '').strip()

    # 匹配申请VPN指令
    apply_vpn_match = re.match(r'/申请vpn\s+(\S+)\s+(.+)', keyword)
    # 匹配激活VPN指令
    activate_vpn_match = re.match(r'/激活vpn\s+(\S+)\s+(\S+)', keyword)

    # 获取房间ID和发送者ID
    roomid = msgJson['wxid'] if '@chatroom' in msgJson['wxid'] else None
    senderid = msgJson['id1'] if roomid else msgJson['wxid']

    # 获取发送者昵称
    nickname = get_member_nick(roomid, senderid) if roomid else 'null'
    applicant = nickname

    if apply_vpn_match:
        email = apply_vpn_match.group(1)
        purpose = apply_vpn_match.group(2)
        logging.info(f"申请VPN: Email={email}, Purpose={purpose}")

        uuid, vpn_account, vpn_password = apply_vpn_account(senderid, email, purpose)

        if uuid is None:
            logging.warning(f"Warning: Email {email} 申请过.")
            ws.send(send_msg(f"Warning: Email {email} 该邮箱已经申请过", roomid=roomid, wxid=senderid, nickname=nickname))
        else:
            # 处理正常情况
            ws.send(send_msg(f"申请成功: VPN 账号 {vpn_account}-激活任务{uuid}", roomid=roomid, wxid=senderid, nickname=nickname))
            groups = get_users_groups()
            if groups:
                combined_string = combine_id_name_to_string(groups)
                logging.info(f"Combined Group String: {combined_string}")
                ws.send(send_msg(f"\n|权限组明细,请管理员分配|\n\n|管理员激活指令 /激活vpn 任务id 权限id|\n{combined_string}", roomid=roomid, wxid=senderid, nickname=nickname))

    elif activate_vpn_match:
        if msgJson['id1']=="p****yuan" or msgJson['id1']=="wxid_y*****omg222" or msgJson['id1']=="ho******05232": ##这里设置管理员的wxid,只有有权限的wxid才可以做激活操作
            uuid = activate_vpn_match.group(1)
            group_id = activate_vpn_match.group(2)
            logging.info(f"激活VPN: UUID={uuid}, Group ID={group_id}")

            record_msg = activate_vpn_account(uuid, group_id,senderid)
            ws.send(send_msg(record_msg, roomid=roomid, wxid=senderid, nickname=nickname))
        else: 
            ws.send(send_msg("无权限", roomid=roomid, wxid=senderid, nickname=nickname))

    else:
        logging.warning("未识别的指令")

###################################################################################
def on_message(ws, message):
    j = json.loads(message)
    resp_type = j['type']

    if resp_type == CHATROOM_MEMBER_NICK:
        handle_nick(j)
    elif resp_type == PERSONAL_DETAIL:
        handle_recv_msg(j)
    elif resp_type == AT_MSG:
        handle_recv_msg(j)
    elif resp_type == DEBUG_SWITCH:
        handle_recv_msg(j)
    elif resp_type == PERSONAL_INFO:
        handle_recv_msg(j)
    elif resp_type == TXT_MSG:
        handle_recv_msg(j)
    elif resp_type == PIC_MSG:
        handle_recv_msg(j)
    elif resp_type == CHATROOM_MEMBER:
        hanle_memberlist(j)
    elif resp_type == RECV_PIC_MSG:
        handle_recv_msg(j)
    elif resp_type == RECV_TXT_MSG:
        handle_recv_msg(j)
    elif resp_type == RECV_TXT_CITE_MSG:
        handleMsg_cite(j)
    elif resp_type == HEART_BEAT:
        heartbeat(j)
    elif resp_type == USER_LIST:
        handle_wxuser_list(j)
    elif resp_type == GET_USER_LIST_SUCCSESS:
        handle_wxuser_list(j)
    elif resp_type == GET_USER_LIST_FAIL:
        handle_wxuser_list(j)
    elif resp_type == JOIN_ROOM:
        welcome_join(j)
    else:
        output(f"Unknown message type: {resp_type}")

while True:
    try:
        ws = websocket.WebSocketApp(SERVER, on_open=on_open, on_message=on_message, on_error=on_error, on_close=on_close)
        ws.run_forever()
    except Exception as e:
        output(f"WebSocket connection failed: {e}. Reconnecting in 5 seconds...")
        time.sleep(5)

wxbot 如果搭建请github找工具

hook监听到的内容

Received message: {'content': '***', 'id': '20240826191727', 'id1': 'to***598', 'id2': '', 'id3': '', 'srvid': 1, 'time': '2024-08-26 19:17:27', 'type': 1, 'wxid': '715****56@chatroom'}

数据库字段表结构

# Host: localhost  (Version: 5.7.26)
# Date: 2024-08-26 19:35:07
# Generator: MySQL-Front 5.3  (Build 4.234)

/*!40101 SET NAMES utf8 */;

#
# Structure for table "fa_vpn_fw_blj"
#

DROP TABLE IF EXISTS `fa_vpn_fw_blj`;
CREATE TABLE `fa_vpn_fw_blj` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uuid` varchar(255) NOT NULL,
  `applicant_wxid` varchar(255) NOT NULL,
  `vpn_account` varchar(255) NOT NULL,
  `vpn_password` varchar(255) NOT NULL,
  `email` varchar(255) NOT NULL,
  `purpose` varchar(255) NOT NULL,
  `application_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `status` varchar(50) DEFAULT '待激活',
  `activator_wxid` varchar(255) DEFAULT NULL,
  `activation_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

结合fastadmin 管理后台

相关推荐
MARIN_shen4 分钟前
Marin说PCB之POC电路layout设计仿真案例---06
网络·单片机·嵌入式硬件·硬件工程·pcb工艺
m0_7482400243 分钟前
Chromium 中chrome.webRequest扩展接口定义c++
网络·c++·chrome
終不似少年遊*1 小时前
华为云计算HCIE笔记05
网络·华为云·云计算·学习笔记·hcie·认证·hcs
蜜獾云1 小时前
docker 安装雷池WAF防火墙 守护Web服务器
linux·运维·服务器·网络·网络安全·docker·容器
小林熬夜学编程2 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
Hacker_Fuchen2 小时前
天融信网络架构安全实践
网络·安全·架构
上海运维Q先生2 小时前
面试题整理15----K8s常见的网络插件有哪些
运维·网络·kubernetes
ProtonBase2 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构
fantasy_arch12 小时前
CPU性能优化-磁盘空间和解析时间
网络·性能优化
是Dream呀14 小时前
Python从0到100(七十八):神经网络--从0开始搭建全连接网络和CNN网络
网络·python·神经网络