【原创】全自动添加防火墙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 管理后台

相关推荐
衍生星球4 小时前
【网络安全】对称密码体制
网络·安全·网络安全·密码学·对称密码
掘根4 小时前
【网络】高级IO——poll版本TCP服务器
网络·数据库·sql·网络协议·tcp/ip·mysql·网络安全
友友马5 小时前
『 Linux 』HTTP(一)
linux·运维·服务器·网络·c++·tcp/ip·http
2401_872514975 小时前
深入探究HTTP网络协议栈:互联网通信的基石
网络·网络协议·http
chenjingming6666 小时前
windows使用tcpdump.exe工具进行抓包教程
网络·测试工具·tcpdump
初黑子zzz6 小时前
rsync
网络
蜗牛学苑_武汉7 小时前
设计模式之代理模式
java·网络·java-ee·代理模式
不良人天码星7 小时前
HTTP 协议的基本格式
网络·网络协议·http
码哝小鱼8 小时前
iptables限制网速
linux·服务器·网络