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

相关推荐
xiaoxiongip6663 分钟前
HTTP 和 HTTPS
网络·爬虫·网络协议·tcp/ip·http·https·ip
JaneJiazhao8 分钟前
HTTPSOK:智能SSL证书管理的新选择
网络·网络协议·ssl
CXDNW9 分钟前
【网络面试篇】HTTP(2)(笔记)——http、https、http1.1、http2.0
网络·笔记·http·面试·https·http2.0
无所谓จุ๊บ1 小时前
树莓派开发相关知识十 -小试服务器
服务器·网络·树莓派
道法自然04021 小时前
Ethernet 系列(8)-- 基础学习::ARP
网络·学习·智能路由器
EasyCVR2 小时前
萤石设备视频接入平台EasyCVR多品牌摄像机视频平台海康ehome平台(ISUP)接入EasyCVR不在线如何排查?
运维·服务器·网络·人工智能·ffmpeg·音视频
明月看潮生3 小时前
青少年编程与数学 02-003 Go语言网络编程 15课题、Go语言URL编程
开发语言·网络·青少年编程·golang·编程与数学
龙哥说跨境4 小时前
如何利用指纹浏览器爬虫绕过Cloudflare的防护?
服务器·网络·python·网络爬虫
懒大王就是我4 小时前
C语言网络编程 -- TCP/iP协议
c语言·网络·tcp/ip
Elaine2023914 小时前
06 网络编程基础
java·网络