Commit f36206bd authored by BH's avatar BH

20201229 linux 添加 vnc 远程

parent 660b2e37
......@@ -206,12 +206,12 @@ class StoreDBBase(object):
def execute(self, sql):
session = self.get_session(self.engine)
cursor = session.execute(sql)
try:
# self.logger.info('execute sql:{}'.format(str(sql)))
session.execute(sql)
except:
log.e("execute sql error:{}".format(traceback.format_exc()))
fields = cursor._metadata.keys
return [dict(zip(fields, item)) for item in cursor.fetchall()]
finally:
cursor.close()
session.close()
......
......@@ -291,22 +291,24 @@ controllers = [
(r'/plugin/free_host', plugin.FreeHostHandler),
# 服务器远程信息
(r'/plugin/session_info', plugin.GetSessionInfoHandler),
# VNC服务器远程信息
(r'/plugin/vnc_session', plugin.GetVNCInfoHandler),
# 绑定支付宝账户
(r'/plugin/bind_list', plugin.BindAccountListHandler),
# 绑定支付宝账户
(r'/plugin/bind_pay_account', plugin.BindPayAccountHandler),
# 密码修改
(r'/plugin/password_update', plugin.PasswordUpdateHandler),
# 账户状态
(r'/plugin/account_status', plugin.AccountStatusHandler),
# 主机监控状态
(r'/plugin/host_monitor', plugin.HostMonitorHandler),
# 账户账户密码获取
# 账户密码获取
(r'/plugin/account_info', plugin.AccountInfoHandler),
# 商城狀態查詢
(r'/plugin/shop_info', plugin.ShopInfoHandler),
# 商城綁定接口
(r'/plugin/shop_bind', plugin.ShopBindHandler),
# 密码修改
(r'/plugin/password_update', plugin.PasswordUpdateHandler),
# 下载地址获取接口
(r'/plugin/bat_download_url', plugin.BatDownloadHandler),
# token 获取
......
# coding: utf-8
import base64
import json
import os
import re
import socket
import hashlib
import time
import binascii
import traceback
import requests
......@@ -21,91 +17,22 @@ from app.base.controller import TPBaseHandler
from app.base.logger import *
from app.const import *
from app.model import plugin
from app.model.plugin import host_id_lock
from app.model.plugin import host_id_lock, add_rsa_key
from app.base.configs import tp_cfg
from pyDes import des, CBC, PAD_PKCS5
import math
from base.plugin import get_plugin_db
from controller import reqparse
from plugin.const import code_comp
from plugin.remote import auto_install_bt, install_docker
import rsa
def current_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
def network_ip():
req = requests.get("http://txt.go.sohu.com/ip/soip")
ip = re.findall(r'\d+.\d+.\d+.\d+', req.text)[0]
return ip
# 秘钥
KEY = 'mHAxsLYz'
def des_encrypt(s, key=None):
"""
DES 加密
:param s: 原始字符串
:return: 加密后字符串,16进制
"""
secret_key = key or KEY
iv = secret_key
k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
en = k.encrypt(s, padmode=PAD_PKCS5)
return binascii.b2a_hex(en)
def des_descrypt(s, key=None):
"""
DES 解密
:param s: 加密后的字符串,16进制
:return: 解密后的字符串
"""
secret_key = key or KEY
iv = secret_key
k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
de = k.decrypt(binascii.a2b_hex(s), padmode=PAD_PKCS5)
return de
def md5(str):
m = hashlib.md5()
b = str.encode(encoding='utf-8')
m.update(b)
return m.hexdigest()
from plugin.utils import current_ip, network_ip, des_encrypt, des_descrypt, md5, is_domain, is_ipv4, KEY, new_keys
def is_domain(domain):
domain_regex = re.compile(
r'(?:[A-Z0-9_](?:[A-Z0-9-_]{0,247}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))\Z',
re.IGNORECASE)
return True if domain_regex.match(domain) else False
def is_ipv4(address):
ipv4_regex = re.compile(
r'(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}',
re.IGNORECASE)
return True if ipv4_regex.match(address) else False
def execute_auto_install_script(ip, usename, password, site, webname, sub_domain):
path = os.path.dirname(os.path.abspath(__file__))
cmd = "nohup /usr/bin/python3 {}/script.py --ip {} -u {} -p {} -s {} -w {} --sub '{}'>> /auto.out &".format(path,
ip,
usename,
password,
site,
webname,
sub_domain)
os.system(cmd)
return cmd
free_cache = {}
token_cache = {}
def execute_add_proxy(ip):
......@@ -113,13 +40,6 @@ def execute_add_proxy(ip):
os.system('nohup /usr/bin/python3 {}/clash_refresh.py --ip {} &'.format(path, ip))
def execute_linux_docker_script(ip, usename, password, site, webname, sub_domain):
path = os.path.dirname(os.path.abspath(__file__))
cmd = "nohup /usr/bin/python3 {}/script.py --ip {} -u {} -p {}>> /auto.out &".format(path, ip, usename, password)
os.system(cmd)
return cmd
class TPBasePluginHandler(TPBaseHandler):
"""
所有返回JSON数据的控制器均从本类继承,返回的数据格式一律包含三个字段:code/msg/data
......@@ -128,6 +48,7 @@ class TPBasePluginHandler(TPBaseHandler):
data: 一般用于成功操作的返回的业务数据
"""
executor = ThreadPoolExecutor(100)
_remote_executor = ThreadPoolExecutor(4)
def __init__(self, application, request, **kwargs):
super().__init__(application, request, **kwargs)
......@@ -139,6 +60,17 @@ class TPBasePluginHandler(TPBaseHandler):
self.release_ip = release_ip
self.parse = None
@run_on_executor(executor='_remote_executor')
def install_bt(self, ip, username, password, site, webname, sub="", host_id=0):
auto_install_bt(ip, username, password, site, webname, sub)
@run_on_executor(executor='_remote_executor')
async def install_docker(self, host_id, ip, username, password):
pubkey = self.rsa_pubkey(host_id)
with open('{}.pem'.format(ip), 'w+') as f:
f.write(pubkey)
install_docker(ip, username, password, pubkey)
async def prepare(self):
# req = requests.get("http://txt.go.sohu.com/ip/soip")
# ip = re.findall(r'\d+.\d+.\d+.\d+', req.text)[0]
......@@ -171,8 +103,7 @@ class TPBasePluginHandler(TPBaseHandler):
except:
raise tornado.web.HTTPError(500)
def check_ip(self, props):
ip = props.get("ip", "")
def check_ip(self, ip):
if ip and not re.findall(r'\d+\.\d+\.\d+\.\d+', ip):
return False
else:
......@@ -188,8 +119,8 @@ class TPBasePluginHandler(TPBaseHandler):
return True
return False
def finish_json(self, code, msg="成功", data=None, pg=None):
_ret = {"res": 1 if not code else 0, "ec": code, "msg": msg}
def finish_json(self, code, msg="", data=None, pg=None):
_ret = {"res": 1 if not code else 0, "ec": code, "msg": msg or code_comp.get(code)}
if data:
_ret["dt"] = {"lst": data}
if pg:
......@@ -224,6 +155,15 @@ class TPBasePluginHandler(TPBaseHandler):
def req_parse(self, required: tuple = ()):
pass
def rsa_pubkey(self, host_id):
pubkey = get_plugin_db().query("tp_remote_host_key", {"host_id": host_id}, "pubkey")
if pubkey:
return pubkey[0].get("pubkey")
pubkey, privkey = new_keys()
add_rsa_key(self, host_id, pubkey, privkey)
return pubkey
@staticmethod
def plugin_query(table, q, fields):
return get_plugin_db().query(table, q, fields)
......@@ -232,6 +172,12 @@ class TPBasePluginHandler(TPBaseHandler):
def plugin_execute(sql):
return get_plugin_db().execute(sql)
@staticmethod
def plugin_param(table, q, fields: str):
data = get_plugin_db().query(table, q, [fields])
data = data[0] if data else {}
return data.get(fields)
class UpdateHostHandler(TPBasePluginHandler):
def generate_assets_num(self, ip, os_type):
......@@ -248,23 +194,23 @@ class UpdateHostHandler(TPBasePluginHandler):
self.parse = reqparse.RequestParser()
self.parse.add_argument("os_type", type=int, help='系统类型', required='host_id' in required, choices=[1, 2])
self.parse.add_argument("host_id", type=int, required='host_id' in required, default=0)
self.parse.add_argument("status", type=int, help='商城域名', default=0)
self.parse.add_argument("status", type=int, help='服务器状态', default=0)
self.parse.add_argument("ip", type=str, help='远程服务器IP', required='ip' in required, trim=True)
self.parse.add_argument("username", type=str, help='服务器用户名', required='username' in required, trim=True)
self.parse.add_argument("password", type=str, help='服务器密码', required='password' in required, trim=True)
self.parse.add_argument("name", type=str, help='服务器命名', trim=True)
self.parse.add_argument("desc", type=str, help='服务器IP', trim=True)
self.parse.add_argument("name", type=str, help='服务器命名', default='')
self.parse.add_argument("desc", type=str, help='服务器IP', default='')
self.parse.add_argument("mch_no", type=str, help='商户号', )
args = self.parse.parse_args(req=self.get_payload())
# todo 检查ip
# if not self.check_ip(props):
# self.finish_json(1001, "IP不符合规范")
# return
if not self.check_ip(args.ip):
self.finish_json(1001, "IP不符合规范")
raise
return args
@tornado.gen.coroutine
def post(self):
args = self.req_parse(('os_type', 'ip', 'username', 'password'))
ip, os_type, name, desc, username, password, status = args.ip, args.os_type, args.name, args.desc, args.username, args.password, args.status
ip, os_type, name, desc, username, password, status, mch_no = args.ip, args.os_type, args.name, args.desc, args.username, args.password, args.status, args.mch_no
assets_num = self.generate_assets_num(ip, os_type)
......@@ -279,10 +225,10 @@ class UpdateHostHandler(TPBasePluginHandler):
resp = yield self.request_api(url, args)
# {"code": 0, "message": "", "data": 7}
if resp.get("code") == 8:
self.finish_json(1003, "已存在主机,不可重复添加")
self.finish_json(2001, "已存在主机,不可重复添加")
return
elif resp.get("code") != 0:
self.finish_json(1003, "添加主机异常")
self.finish_json(2001, "添加主机异常")
return
host_id = resp.get("data")
......@@ -298,7 +244,7 @@ class UpdateHostHandler(TPBasePluginHandler):
resp = yield self.request_api(url, args)
if resp.get("code") != 0:
self.finish_json(1004, "添加服务器账户异常")
self.finish_json(2002, "添加服务器账户异常")
return
# acc_id = resp.get("data")
......@@ -312,7 +258,13 @@ class UpdateHostHandler(TPBasePluginHandler):
self.finish_json(0, "成功")
execute_add_proxy(ip)
else:
self.finish_json(1002, "记录主机信息失败")
self.finish_json(2001, "记录主机信息失败")
# 商家平台手动绑定 第一步,添加空的绑定记录
if mch_no:
args = {"mch_no": mch_no, "comp_id": 0, "host_id": host_id, "host_assigned": 0,
"account_source": 1, "account": "", "login_status": 0, "mch_name": "", "password": ""}
err, info = plugin.add_account_host_bind(self, args)
@tornado.gen.coroutine
def put(self):
......@@ -332,14 +284,14 @@ class UpdateHostHandler(TPBasePluginHandler):
resp = yield self.request_api(url, args)
# {"code": 0, "message": "", "data": 7}
if resp.get("code") != 0:
self.finish_json(1003, "修改主机异常")
self.finish_json(2001, "修改主机异常")
return
old_username = self.query("remote_host", "username", {"id": host_id})
acc_id = self.query("acc", "id", {"host_id": host_id, "username": old_username})
if not acc_id:
self.finish_json(1011, "未发现该服务器信息")
self.finish_json(2001, "未发现该服务器信息")
return
url = "http://127.0.0.1:7190/asset/update-account"
......@@ -355,7 +307,7 @@ class UpdateHostHandler(TPBasePluginHandler):
resp = yield self.request_api(url, args)
if resp.get("code") != 0:
self.finish_json(1004, "添加服务器账户异常")
self.finish_json(2002, "添加服务器账户异常")
return
password = des_encrypt(password).decode()
......@@ -367,11 +319,11 @@ class UpdateHostHandler(TPBasePluginHandler):
# 已经存在数据库
if err == TPE_NOT_EXISTS:
self.finish_json(1001, "修改服务器不存在")
self.finish_json(2001, "修改服务器不存在")
elif err == TPE_DATABASE:
self.finish_json(1001, "修改服务器失败")
self.finish_json(2001, "修改服务器失败")
elif err == TPE_FAILED:
self.finish_json(1001, "服务器禁用不可修改")
self.finish_json(2001, "服务器禁用不可修改")
else:
self.finish_json(0, "成功")
......@@ -383,14 +335,14 @@ class GetHostListHandler(TPBasePluginHandler):
self.parse.add_argument("status", type=int, help='', default=0)
self.parse.add_argument("pageIndex", type=int, help='', required=True, )
self.parse.add_argument("pageSize", type=int, help='', required=True, )
self.parse.add_argument("ip", type=str, help='', trim=True)
self.parse.add_argument("name", type=str, help='', trim=True)
self.parse.add_argument("ip", type=str, help='', )
self.parse.add_argument("name", type=str, help='', )
args = self.parse.parse_args(req=self.get_payload())
return args
async def post(self):
args = self.req_parse()
page_index, page_size, os_type, ip, name, status = args.page_index, args.page_size, args.os_type, args.ip, args.name, args.status
page_index, page_size, os_type, ip, name, status = args.pageIndex, args.pageSize, args.os_type, args.ip, args.name, args.status
sql_limit = dict()
sql_limit['page_index'] = page_index - 1 if page_index - 1 > 0 else 0
......@@ -437,23 +389,23 @@ class GetHostInfoHandler(TPBasePluginHandler):
args = self.req_parse()
host_id, mch_no = args.host_id, args.mch_no
q = {}
if host_id:
q.update({"id": host_id})
q = {"id": host_id}
elif mch_no:
q.update({"mch_no": mch_no})
q = {"mch_no": mch_no}
else:
return self.finish_json("1002", msg="传入参数异常")
return self.finish_json(1001)
data = self.plugin_query("tp_remote_account_host_bind", q,
['id', 'mch_no', 'comp_id', 'host_id', 'host_assigned', 'account_source',
'account', 'login_status', 'mch_name', 'create_time'])
if not data:
if mch_no and not host_id:
# 商家平台 通过商户请求
# 商家平台 通过商户请求,允许返回空的绑定信息
return self.finish_json(0, data=[])
else:
return self.finish_json("1004", msg="设备信息获取异常")
return self.finish_json(2001, msg="设备信息获取异常")
# mch_no 是否存在多个绑定
host_id = host_id or data[0]['host_id']
......@@ -471,15 +423,28 @@ class GetHostInfoHandler(TPBasePluginHandler):
class FreeHostHandler(TPBasePluginHandler):
async def get(self):
def req_parse(self, required: tuple = ()):
self.parse = reqparse.RequestParser()
self.parse.add_argument("mch_no", type=int, help='', default=0, required=True, )
args = self.parse.parse_args(req=self.get_payload())
return args
async def post(self):
args = self.req_parse()
mch_no = args.mch_no
sql = """select a.id as id
from tp_remote_host a LEFT join tp_remote_account_host_bind b on a.id = b.host_id
where b.id is null and os_type={}""".format(2)
data = self.plugin_execute(sql)
if data:
return self.finish_json(0, data=[data[0]])
host_ids = [_["id"] for _ in data]
_cache = free_cache.get(mch_no)
host_id = _cache if _cache in host_ids else host_ids[0]
data = self.plugin_query("tp_remote_host", {"id": host_id}, ["ip", "id", "os_type", "username"])
free_cache[mch_no] = host_id
return self.finish_json(0, data=data)
else:
return self.finish_json(1001, msg="无空闲主机")
return self.finish_json(2001, msg="无空闲主机")
class GetSessionInfoHandler(TPBasePluginHandler):
......@@ -492,13 +457,12 @@ class GetSessionInfoHandler(TPBasePluginHandler):
self.parse.add_argument("ip", type=str, help='', )
self.parse.add_argument("username", type=str, help='', )
self.parse.add_argument("password", type=str, help='', trim=True)
self.parse.add_argument("password", type=str, help='', )
args = self.parse.parse_args(req=self.get_payload())
# todo
# if not self.check_ip(props):
# self.finish_json(1001, "IP不符合规范")
# return
if not self.check_ip(args.ip):
self.finish_json(1001, "IP不符合规范")
raise
return args
async def post(self):
......@@ -520,7 +484,7 @@ class GetSessionInfoHandler(TPBasePluginHandler):
err, info, host_id = plugin.get_session_info(host_id, mch_no, ip)
if err == TPE_NOT_EXISTS:
self.finish_json(1002, "未发现请求主机信息")
self.finish_json(2001, "未发现请求主机信息")
return
os_type = self.query("host", "os_type", {"id": host_id})
......@@ -559,8 +523,7 @@ class GetSessionInfoHandler(TPBasePluginHandler):
"protocol_flag": resp.get("data", {}).get("protocol_flag")}
resp = """http://localhost:50022/api/run/{}""".format(json.dumps(resp))
self.finish_json(0, data=[resp])
return
return self.finish_json(0, data=[resp])
class BindAccountListHandler(TPBasePluginHandler):
......@@ -570,10 +533,6 @@ class BindAccountListHandler(TPBasePluginHandler):
self.parse.add_argument("account", type=str, help='', )
self.parse.add_argument("account_source", type=int, help='', )
args = self.parse.parse_args(req=self.get_payload())
# todo
# if not self.check_ip(props):
# self.finish_json(1001, "IP不符合规范")
# return
return args
async def post(self):
......@@ -631,73 +590,80 @@ class BindPayAccountHandler(TPBasePluginHandler):
self.parse.add_argument("account", type=str, required=True, help='', )
self.parse.add_argument("password", type=str, required=True, help='', )
# 绑定账户类型
args = self.parse.parse_args(req=self.get_payload())
# todo
# if not self.check_ip(props):
# self.finish_json(1001, "IP不符合规范")
# return
return args
async def post(self):
args = self.req_parse()
mch_no, host_id, account, password, comp_id, biz_id = args.mch_no, args.host_id, args.account, args.password, args.comp_id, args.biz_id
status = self.query("remote_host", "status", {"id": host_id})
# 新增逻辑
if not password:
host_id_lock.pop(host_id, None)
self.finish_json(1001, "缺少必要参数异常:password")
return
item = self.plugin_query("tp_remote_account_host_bind", {"host_id": host_id, "account": account}, ['id'])
if item:
return self.finish_json(2002, "该账号已绑定")
item = self.plugin_query("tp_remote_host", {"id": host_id}, ['id', 'ip', 'username', 'password'])
if not item:
return self.finish_json(2001, "绑定设备不存在")
id = self.query("remote_account_host_bind", "id", {"host_id": host_id, "mch_no": mch_no})
_comp_id = self.plugin_param("tp_remote_account_host_bind", {"host_id": host_id}, "comp_id")
if comp_id and _comp_id and comp_id != _comp_id:
return self.finish_json(2002, "不可跨公司资质绑定")
des_password = des_encrypt(password).decode()
args = {"mch_no": mch_no, "comp_id": comp_id, "host_id": host_id, "host_assigned": 0,
"account_source": 1, "account": account, "login_status": 0, "mch_name": "", "password": des_password}
err, info = plugin.add_account_host_bind(self, args)
if id:
old_account = self.query('remote_account_host_bind', "account", {"id": id})
if account and old_account and account != old_account:
self.finish_json(1011, "不允许修改绑定账户")
host_id_lock.pop(host_id, None)
return
self.finish_json(0)
err = plugin.update_account_host_bind(self, args)
if err == TPE_FAILED:
self.finish_json(1011, "不允许修改公司资质")
elif err == TPE_NOT_EXISTS:
self.finish_json(1012, "设备不存在")
elif int(status) != 1:
self.finish_json(2001, "请先初始化主机环境,再绑定操作")
elif err == TPE_OK:
self.finish_json(0)
host_id_lock.pop(host_id, None)
item = item[0]
password = des_descrypt(item.get("password", "")).decode()
await self.add_members(biz_id, host_id)
await self.install_docker(host_id, item['ip'], item['username'], password)
async def put(self):
args = self.req_parse()
mch_no, host_id, account, password, comp_id, biz_id = args.mch_no, args.host_id, args.account, args.password, args.comp_id, args.biz_id
if not account:
self.finish_json(1001, "传入密码异常")
return
if not password:
item = self.plugin_query("tp_remote_account_host_bind", {"host_id": host_id, "mch_no": mch_no},
['id', 'account', 'comp_id'])
# 云平台 mch_no 为可选
if not item:
item = self.plugin_query("tp_remote_account_host_bind", {"host_id": host_id}, ['id', 'account', 'comp_id'])
# 修改逻辑
if not item:
self.finish_json(2002, "设备不存在")
return
# 允许未绑定账户的主机进行修改
if item['account'] and account != item['account']:
self.finish_json(2002, "不允许修改绑定账户")
host_id_lock.pop(host_id, None)
self.finish_json(1001, "缺少必要参数异常:password")
return
# args['password'] = des_password
err, info = plugin.add_account_host_bind(self, args)
if item['comp_id'] != comp_id:
self.finish_json(2002, "不允许修改公司资质")
host_id_lock.pop(host_id, None)
return
if err == TPE_EXISTS:
self.finish_json(1011, "绑定的支付宝账户已存在")
elif err == TPE_FAILED:
self.finish_json(1011, "不可跨资质绑定")
elif err == TPE_NOT_EXISTS:
self.finish_json(1011, "绑定设备不存在")
elif err == TPE_DATABASE:
self.finish_json(1011, "绑定账户异常,请检查请求参数")
elif int(status) != 1:
self.finish_json(2001, "请先初始化主机环境,再绑定操作")
else:
self.finish_json(0)
des_password = des_encrypt(password).decode()
err = plugin.update("tp_remote_account_host_bind",
{"password": des_password, "biz_id": biz_id, "comp_id": comp_id},
{"host_id": host_id, "account": account})
self.finish_json(0)
host_id_lock.pop(host_id, None)
if err != TPE_OK:
return
await self.add_members(biz_id, host_id)
# todo 添加容器初始化功能
# execute_linux_docker_script()
class AccountStatusHandler(TPBasePluginHandler):
......@@ -719,7 +685,7 @@ class AccountStatusHandler(TPBasePluginHandler):
host_id = self.query("host", ["id"], {"ip": ip})
if not host_id:
self.finish_json(1011, "未发现该服务器信息")
self.finish_json(2001, "未发现该服务器信息")
return
plugin.update("tp_remote_account_host_bind", {"mch_name": mch_name, "login_status": login_status},
......@@ -794,14 +760,13 @@ class ShopBindHandler(TPBasePluginHandler):
self.parse.add_argument("host_id", type=int, help='绑定设备id', required=True, trim=True)
self.parse.add_argument("name", type=str, help='商城名称', required=True, trim=True)
self.parse.add_argument("domain", type=str, help='商城域名', required=True, trim=True)
self.parse.add_argument("sub_domain", type=str, help='商城二级域名', trim=True)
self.parse.add_argument("sub_domain", type=str, help='商城二级域名', )
args = self.parse.parse_args(req=self.get_payload())
return args
async def post(self):
try:
args = self.req_parse()
# todo 特殊检查
if not is_domain(args.domain) and not is_ipv4(args.domain):
self.finish_json(1001, "请填写正确的域名信息")
return
......@@ -830,9 +795,9 @@ class ShopBindHandler(TPBasePluginHandler):
items = plugin.query_one("remote_host", ['ip', 'username', 'password'], {"id": args.host_id, })
password = des_descrypt(items.get("password", "")).decode()
cmd = execute_auto_install_script(items.get("ip", ""), items.get("username", ""), password, args.domain,
args.name, args.sub_domain)
log.i("自动部署商城:{}".format(cmd))
await self.install_bt(items.get("ip", ""), items.get("username", ""), password, args.domain, args.name,
args.sub_domain)
log.i("自动部署商城:{}".format(""))
self.finish_json(0)
return
......@@ -853,9 +818,10 @@ class ShopBindHandler(TPBasePluginHandler):
# finish 调用商城自动部署
items = plugin.query_one("remote_host", ['ip', 'username', 'password'], {"id": args.host_id, })
password = des_descrypt(items.get("password", "")).decode()
cmd = execute_auto_install_script(items.get("ip", ""), items.get("username", ""), password, args.domain,
args.name, args.sub_domain)
log.i("自动部署商城:{}".format(cmd))
await self.install_bt(items.get("ip", ""), items.get("username", ""), password, args.domain, args.name,
args.sub_domain)
# todo
log.i("自动部署商城:{}".format(""))
else:
self.finish_json(1002, "绑定商城信息失败")
except:
......@@ -984,17 +950,83 @@ class TokenHandler(TPBasePluginHandler):
def req_parse(self, required: tuple = ()):
self.parse = reqparse.RequestParser()
self.parse.add_argument("ip", type=str, help='请求ip', required=True, trim=True)
self.parse.add_argument("host_id", type=int, help='请求主机id')
# 商家平台
args = self.parse.parse_args(req=self.get_payload())
return args
async def post(self):
try:
# todo 限制访问ip
args = self.req_parse()
agent = self.request.headers.get("User-agent")
log.i("token md5:{} \n".format(agent + args.ip))
token = md5(agent + args.ip)
self.finish_json(0, "成功", data=[{"token": token}])
timestamp = int(time.time())
log.i("token md5:{} \n".format(agent + args.ip + str(timestamp)))
token = md5(agent + args.ip + str(timestamp))
privkey = self.plugin_param('tp_remote_host_key', {"host_id": args.host_id}, "privkey")
if not privkey:
self.finish_json(1001, "host_id 未绑定账户", data=[])
return
privkey = rsa.PrivateKey.load_pkcs1(privkey.encode())
crypto_email_text = rsa.sign(token.encode(), privkey, 'SHA-1')
msg = base64.b64encode(crypto_email_text).decode()
# 读取远程主机ip
url = "http://172.30.20.120:8080/token"
resp = requests.post(url, headers={"user-agent": agent},
data={"ip": args.ip, "msg": msg, "timestamp": timestamp})
print(resp.text)
if resp.text == 'ok':
token_cache[args.ip] = token
self.finish_json(0, "成功", data=[{"token": token}])
else:
self.finish_json(1001, "失败", data=[])
except:
info = traceback.format_exc()
log.e("设备详情,异常信息:{}".format(info))
class GetVNCInfoHandler(TPBasePluginHandler):
def req_parse(self, required: tuple = ()):
self.parse = reqparse.RequestParser()
self.parse.add_argument("username", type=str, help='登录用户名', required=True, trim=True)
self.parse.add_argument("host_id", type=int, help='请求主机id')
# 商家平台
args = self.parse.parse_args(req=self.get_payload())
return args
async def post(self):
try:
args = self.req_parse()
host_id, username = args.host_id, args.username
if not host_id or not username:
self.finish_json(1001)
return
password = self.plugin_param("tp_remote_account_host_bind", {"host_id": host_id, "account": username},
"password")
password = des_descrypt(password).decode()
ip = self.plugin_param("tp_remote_host", {"id": host_id, }, "ip")
pubkey = self.plugin_param("tp_remote_host_key", {"host_id": host_id, }, "pubkey")
log.i(pubkey)
log.i(md5(pubkey.strip()))
# todo token的有效时常
token = token_cache.get(ip)
if not token:
url = "http://127.0.0.1:7190/plugin/token"
# todo 获取发起请求ip
await self.request_api(url, json={"ip": ip, "host_id": host_id})
token = token_cache.get(ip)
# todo
# url = "http://{}:8000/login".format("127.0.0.1")
url = "http://{}:8080/login".format(ip)
log.i(md5(pubkey)[:8])
body = base64.b64encode(
des_encrypt(json.dumps({"account": username, "password": password}),
key=md5(pubkey.strip())[:8])).decode()
resp = requests.post(url, data={"body": body}, cookies={'token': token})
log.i(resp.text)
# todo 添加账户密码信息
url = "http://{}:8083/vnc.html".format(ip)
self.finish_json(0, data=[url])
return
except:
info = traceback.format_exc()
log.e("设备详情,异常信息:{}".format(info))
......@@ -1004,17 +1036,18 @@ class PushStatusHandler(TPBasePluginHandler):
def req_parse(self, required: tuple = ()):
self.parse = reqparse.RequestParser()
self.parse.add_argument("module", type=str, help='推送模块', required=True, trim=True)
self.parse.add_argument("unique_id", type=int, help='请求模块', required=True, )
self.parse.add_argument("status", type=int, help='请求模块', default=0)
self.parse.add_argument("unique_id", type=str, help='唯一标识', required=True, )
self.parse.add_argument("status", type=int, help='上报状态', default=0)
self.parse.add_argument("remark", type=str, help='上报备注', default="")
args = self.parse.parse_args(req=self.get_payload())
return args
async def post(self):
try:
args = self.req_parse()
module, unique_id, status = args.module, args.unique_id, args.status
module, unique_id, status, remark = args.module, args.unique_id, args.status, args.remark
if module == "linux_docker":
# todo 存储容器状态
plugin.update("tp_remote_host", {"bind_status": status, "bind_remark": remark}, {"ip": unique_id})
self.finish_json(0, "成功", data=[])
except:
info = traceback.format_exc()
......
......@@ -35,7 +35,7 @@ def update_shop_info(site, webname, host_id=0, status=2, **kwargs):
def install_shop(ssh, site, ip, webname, sub=""):
stdin, stdout, stderr = ssh.exec_command("sh /root/install-shop.sh {} {} {} '{}'".format(site, ip, webname, sub))
stdin, stdout, stderr = ssh.exec_command("sh /root/install-shop_build.sh {} {} {} '{}'".format(site, ip, webname, sub))
# 获取命令结果
res, err = stdout.read(), stderr.read()
......@@ -71,7 +71,7 @@ def push_file(ip, username, password):
try:
p.put('{}/install-bt.sh'.format(path), '/root/install-bt.sh') # 上传文件到远程机
p.put('{}/auto_install.py'.format(path), '/root/auto_install.py') # 上传文件到远程机
p.put('{}/install-shop.sh'.format(path), '/root/install-shop.sh') # 上传文件到远程机
p.put('{}/install-shop_build.sh'.format(path), '/root/install-shop_build.sh') # 上传文件到远程机
finally:
nimei.close()
p.close()
......
......@@ -24,7 +24,7 @@ def get_host_list(sql_limit, os_type, ip, search, status):
s = SQL(get_db())
s.select_from('remote_host',
['id', 'assets_num', 'os_type', 'ip', 'status', 'username', 'password', 'name', 'remark',
'create_time'],
'create_time', 'bind_status', 'bind_remark'],
alt_name='h')
str_where = ''
......@@ -186,27 +186,6 @@ def add_account_host_bind(handler, args):
_time_now = tp_timestamp_utc_now()
operator = handler.get_current_user()
# 1. 判断账户是否已经存在了
# 1.1 todo 禁用主机可以重新绑定
if args.get("account", ""):
sql = 'SELECT id FROM tp_remote_account_host_bind WHERE account="{}" '.format(args['account'])
db_ret = db.query(sql)
if db_ret is not None and len(db_ret) > 0:
return TPE_EXISTS, 0
# 2. 判断host_id 是否存在
sql = 'SELECT id FROM tp_remote_host WHERE id="{}"'.format(args['host_id'])
db_ret = db.query(sql)
if not db_ret:
return TPE_NOT_EXISTS, 0
# 3. 是否已经存在不同资质公司
sql = 'SELECT comp_id FROM tp_remote_account_host_bind WHERE host_id={}'.format(args['host_id'])
db_ret = db.query(sql)
comp_id = args.get('comp_id')
if db_ret and (comp_id,) not in db_ret:
return TPE_FAILED, 0
sql = 'INSERT INTO `tp_remote_account_host_bind` (biz_id,mch_no, comp_id, host_id, host_assigned, account_source, account, password, login_status, mch_name, create_time, create_by, update_time, update_by) VALUES ' \
'({biz_id},"{mch_no}", {comp_id}, {host_id}, {host_assigned}, {account_source}, "{account}", "{password}", {login_status},"{mch_name}", "{create_time}", "{create_by}", "{update_time}", "{update_by}");' \
''.format(mch_no=args.get("mch_no", ""), comp_id=args.get("comp_id", 0), host_id=args.get("host_id", 0),
......@@ -231,57 +210,6 @@ def add_account_host_bind(handler, args):
return TPE_OK, _id
def update_account_host_bind(handler, args):
db = get_db()
_time_now = tp_timestamp_utc_now()
operator = handler.get_current_user()
# 1. 判断账户是否已经存在了
sql = 'SELECT comp_id FROM tp_remote_account_host_bind WHERE host_id="{}"'.format(args['host_id'])
db_ret = db.query(sql)
if not db_ret:
return TPE_NOT_EXISTS
# 不允许修改公司资质
if str(db_ret[0][0]) != str(args['comp_id']):
return TPE_FAILED
# 2. 判断host_id 是否存在
sql = 'SELECT id FROM tp_remote_account_host_bind WHERE host_id="{}" and mch_no = "{}"'.format(args['host_id'],
args['mch_no'])
db_ret = db.query(sql)
if not db_ret:
return TPE_NOT_EXISTS
id = db_ret[0][0]
sql = 'UPDATE `tp_remote_account_host_bind` SET ' \
'`mch_no`="{mch_no}", `comp_id`={comp_id}, `host_id`="{host_id}", ' \
'`host_assigned`={host_assigned}, `account_source`="{account_source}", `account`="{account}", ' \
'`login_status`="{login_status}", `mch_name`="{mch_name}", `update_time`="{update_time}", `update_by`="{update_by}" WHERE `id`={id};' \
.format(mch_no=args.get("mch_no", ""), comp_id=args.get("comp_id", 0), host_id=args.get("host_id", 0),
host_assigned=args.get("host_assigned", 0), account_source=args.get("account_source", 1),
account=args.get("account", ""), password=args.get("password", ""),
login_status=args.get("login_status", 0), mch_name=args.get("mch_name", ""),
update_time=_time_now, update_by=operator['id'], id=id)
db_ret = db.exec(sql)
if not db_ret:
return TPE_DATABASE
if args.get("password"):
sql = 'UPDATE `tp_remote_account_host_bind` SET `password` = "{password}" WHERE `id`={id};' \
.format(password=args.get("password", ""), id=id)
db_ret = db.exec(sql)
if not db_ret:
return TPE_DATABASE
# operator = handler.get_current_user()
# syslog.sys_log(operator, handler.request.remote_ip, TPE_OK, "更新用户信息:{}".format(args['username']))
return TPE_OK
def get_session_info(host_id, mch_no, ip):
db = get_db()
......@@ -403,3 +331,28 @@ def add_shop_bind(handler, args):
# 更新主机相关账号数量
return TPE_OK, _id
def add_rsa_key(handler, host_id, pubkey, privkey):
db = get_db()
_time_now = tp_timestamp_utc_now()
operator = handler.get_current_user()
sql = 'INSERT INTO `tp_remote_host_key` (host_id, pubkey, privkey, type, status,create_time, create_by, update_time, update_by) VALUES ' \
'({host_id}, "{pubkey}", "{privkey}", "{type}", {status},{create_time}, "{create_by}", {update_time}, "{update_by}");' \
''.format(host_id=host_id, pubkey=pubkey, privkey=privkey, type="RSA", status=1, create_time=_time_now,
create_by=operator['id'], update_time=_time_now, update_by=operator['id'])
db_ret = db.exec(sql)
if not db_ret:
return TPE_DATABASE, 0
_id = db.last_insert_id()
# acc_name = '{}@{}'.format(args['username'], args['host_ip'])
# if len(args['router_ip']) > 0:
# acc_name += '(由{}:{}路由)'.format(args['router_ip'], args['router_port'])
# syslog.sys_log(operator, handler.request.remote_ip, TPE_OK, "创建账号:{}".format(acc_name))
# 更新主机相关账号数量
return TPE_OK, _id
# coding: utf-8
code_comp = {
0: "成功",
2: "失败",
1001: "传入不符合接口规范", # 可自填
2001: "主机信息异常",
2002: "账户信息异常",
2003: "企业信息异常"
}
# coding: utf-8
import os
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("--no-sandbox")
# options.add_argument('--disable-dev-shm-usage')
options.add_experimental_option("excludeSwitches", ['enable-automation'])
# options.add_argument("--remote-debugging-port=9222")
# options.headless = True
command_executor = "http://localhost:4444/wd/hub"
driver = webdriver.Remote(command_executor, desired_capabilities=options.to_capabilities())
import time
# time.sleep(5)
driver.get("https://b.alipay.com/index2.htm")
driver.get_screenshot_as_file('screenshot1.png')
import random
def do_tool(k):
os.system("xdotool key {}".format(k))
time.sleep(random.randint(1, 10) * 0.1)
[do_tool(k) for k in '18826140775']
do_tool("Tab")
[do_tool(k) for k in 'v4f8169l']
do_tool("Tab")
do_tool("Tab")
do_tool("Tab")
do_tool("Shift+Tab")
do_tool("Shift+Tab")
# 获取截图
driver.get_screenshot_as_file('screenshot.png')
import cv2
def crop_code(img_path):
img = cv2.pyrDown(cv2.imread(img_path, cv2.IMREAD_UNCHANGED))
img2 = cv2.imread(img_path)
ret, thresh = cv2.threshold(cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)
contours, hier = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
x, y, w, h = cv2.boundingRect(c)
if w < 30 or h < 20 or w * h > 1000:
continue
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cropImg = img2[y * 2:(y + h) * 2, x * 2:(x + w) * 2]
cv2.imwrite("code.png", cropImg)
return "code.png"
path = crop_code("screenshot0.png")
from example import FateadmApi
pd_id = "122334"
pd_key = "CvSAzmpNTCk953nPqrciORQ5LaMmwsSZ"
app_id = "322334"
app_key = "ZVZG1lpunkJrrGA0xPJJgfRHHa384ycQ"
pred_type = "30400"
# 初始化api接口
other_api = FateadmApi(app_id, app_key, pd_id, pd_key)
rsp = other_api.PredictFromFile("30400", "code.png", "demo")
code = rsp.pred_rsp.value
print(code)
[do_tool(k) for k in code]
do_tool("KP_Enter")
FROM ubuntu:18.04
RUN set -xe \
&& echo '#!/bin/sh' > /usr/sbin/policy-rc.d \
&& echo 'exit 101' >> /usr/sbin/policy-rc.d \
&& chmod +x /usr/sbin/policy-rc.d \
&& dpkg-divert --local --rename --add /sbin/initctl \
&& cp -a /usr/sbin/policy-rc.d /sbin/initctl \
&& sed -i 's/^exit.*/exit 0/' /sbin/initctl \
&& echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \
&& echo 'DPkg::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' > /etc/apt/apt.conf.d/docker-clean \
&& echo 'APT::Update::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' >> /etc/apt/apt.conf.d/docker-clean \
&& echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' >> /etc/apt/apt.conf.d/docker-clean \
&& echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/docker-no-languages \
&& echo 'Acquire::GzipIndexes "true"; Acquire::CompressionTypes::Order:: "gz";' > /etc/apt/apt.conf.d/docker-gzip-indexes \
&& echo 'Apt::AutoRemove::SuggestsImportant "false";' > /etc/apt/apt.conf.d/docker-autoremove-suggests
RUN rm -rf /var/lib/apt/lists/*
RUN mkdir -p /run/systemd \
&& echo 'docker' > /run/systemd/container
CMD ["/bin/bash"]
ENV HOME=/root
#ENV DEBIAN_FRONTEND=noninteractive
#ENV LC_ALL=C.UTF-8
#ENV LANG=zh_CN.UTF-8
#ENV LANGUAGE=zh_CN.UTF-8
ENV TZ=Asia/Shanghai
#ENV SCREEN_RESOLUTION=1280x900
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
&& echo $TZ > /etc/timezone
RUN sed -i "s@http://deb.debian.org@http://mirrors.aliyun.com@g" /etc/apt/sources.list && rm -Rf /var/lib/apt/lists/*
RUN apt-get update \
&& apt-get -y install xvfb x11vnc supervisor fluxbox git-core git ttf-wqy-microhei ttf-wqy-zenhei xfonts-wqy wget gnupg unzip fcitx-bin fcitx-table fcitx-pinyin
#
# 設定輸入法預設切換熱鍵 SHIFT-SPACE
#
RUN mkdir -p /root/.config/fcitx && \
echo [Hotkey] > /root/.config/fcitx/config && \
echo TriggerKey=SHIFT_SPACE >> /root/.config/fcitx/config \
echo SwitchKey=Disabled >> /root/.config/fcitx/config \
echo IMSwitchKey=False >> /root/.config/fcitx/config
#http://dl.google.com/linux/chrome/deb/
#https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
#https://dl.google.com/linux/direct/google-chrome-stable_current_i386.deb
#RUN echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \
# && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
# && apt-get update \
# && apt-get install google-chrome-stable -y \
# && apt-get install -fy
#===============
# Google Chrome
#===============
RUN mkdir -p /etc/opt
COPY google-chrome-stable_current_amd64.deb /etc/opt/google-chrome-stable_current_amd64.deb
RUN apt install -fy /etc/opt/google-chrome-stable_current_amd64.deb
RUN apt-get autoclean
WORKDIR /root
RUN git clone https://github.com/novnc/noVNC.git \
&& ln -s /root/noVNC/vnc_auto.html /root/noVNC/index.html
#==============================
# Java8 - OpenJDK JRE headless
# Minimal runtime used for executing non GUI Java programs
#==============================
# Regarding urandom see
# http://stackoverflow.com/q/26021181/511069
# https://github.com/SeleniumHQ/docker-selenium/issues/14#issuecomment-67414070
RUN apt -qqy update \
&& apt -qqy install \
openjdk-8-jre-headless \
&& sed -i '/securerandom.source=/ s|/dev/u?random|/dev/./urandom|g' \
/etc/java-*/security/java.security \
&& apt -qyy autoremove \
&& rm -rf /var/lib/apt/lists/* \
&& apt -qyy clean
#=================
# Selenium latest
#=================
ARG SEL_DIRECTORY="3.14"
ENV SEL_VER="3.141.59"
RUN wget -nv "https://github.com/dosel/selenium/releases/download/selenium-3.141.59-patch-d47e74d6f2/selenium.jar" \
&& ln -s "selenium.jar" \
"selenium-server-standalone-${SEL_VER}.jar" \
&& ln -s "selenium.jar" \
"selenium-server-standalone-3.jar"
#==================
# Chrome webdriver
#==================
# How to get cpu arch dynamically: $(lscpu | grep Architecture | sed "s/^.*_//")
ARG CHROME_DRIVER_VERSION="78.0.3904.70"
ENV CHROME_DRIVER_BASE="chromedriver.storage.googleapis.com" \
CPU_ARCH="64"
ENV CHROME_DRIVER_FILE="chromedriver_linux${CPU_ARCH}.zip"
ENV CHROME_DRIVER_URL="https://${CHROME_DRIVER_BASE}/${CHROME_DRIVER_VERSION}/${CHROME_DRIVER_FILE}"
# Gets latest chrome driver version. Or you can hard-code it, e.g. 2.15
RUN wget -nv -O chromedriver_linux${CPU_ARCH}.zip ${CHROME_DRIVER_URL}
RUN unzip chromedriver_linux${CPU_ARCH}.zip
RUN rm chromedriver_linux${CPU_ARCH}.zip \
&& mv chromedriver \
chromedriver-${CHROME_DRIVER_VERSION} \
&& chmod 755 chromedriver-${CHROME_DRIVER_VERSION} \
&& ln -s chromedriver-${CHROME_DRIVER_VERSION} \
chromedriver \
&& ln -s /root/chromedriver /usr/bin
RUN apt-get update && apt-get install -y \
autoconf \
automake \
libtool \
libffi-dev \
git \
libssl-dev \
xorg-dev \
libvncserver-dev \
dbus-x11
# download source code
RUN cd /root && mkdir src && cd src && git clone https://github.com/LibVNC/x11vnc
# compile and install , default install path /usr/local/bin/x11vnc
RUN apt-get remove -y x11vnc \
&& cd /root/src/x11vnc \
&& autoreconf -fiv \
&& ./configure \
&& make \
&& make install
#==================
# python3
#==================
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
&& pip3 install selenium selenium-requests
#==================
# nginx
#==================
RUN apt-get update && apt-get install -y \
libpcre3-dev \
openssl \
ruby \
zlib1g \
zlib1g.dev
RUN cd /root &&wget https://openresty.org/download/openresty-1.13.6.2.tar.gz && tar xzvf openresty-1.13.6.2.tar.gz \
&& cd openresty-1.13.6.2/ \
&& ./configure \
&& make \
&& make install \
&& ln /usr/local/openresty/nginx/sbin/nginx /usr/bin/nginx \
&& var=" include /usr/local/openresty/nginx/conf/conf.d/*.conf;" \
&& sed -i "/gzip on;/ a\\$var" /usr/local/openresty/nginx/conf/nginx.conf \
&& var=" lua_shared_dict my_cache 64m;" \
&& sed -i "/gzip on;/ a\\$var" /usr/local/openresty/nginx/conf/nginx.conf \
&& mkdir -p /usr/local/openresty/nginx/conf/conf.d \
&& mkdir -p /var/log/nginx
COPY api-redir.conf /usr/local/openresty/nginx/conf/conf.d/api-redir.conf
COPY cache-redir.conf /usr/local/openresty/nginx/conf/conf.d/cache-redir.conf
#==================
# xdotool 自动化工具
#==================
RUN apt-get update && apt-get install -y \
xdotool \
cmake
RUN pip3 install scikit-build && pip3 install opencv-python requests flask rsa
COPY vnc-redir.conf /usr/local/openresty/nginx/conf/conf.d/vnc-redir.conf
RUN mkdir -p /root/main
COPY public.pem /root/main/public.pem
COPY small_web.py /root/main/small_web.py
RUN pip3 install pyDes
#==================
# 调试用工具
#==================
RUN apt-get update && apt-get install -y \
lsof \
vim
ENV \
# 時區
TZ=Asia/Shanghai \
# 系統語系
LANG=zh_CN.UTF-8 \
LANGUAGE=zh_CN \
LC_ALL=zh_CN.UTF-8 \
# 輸入法
XMODIFIERS="@im=fcitx" \
GTK_IM_MODULE=fcitx \
QT_IM_MODULE=fcitx \
#
DISPLAY=:0 \
SCREEN_RESOLUTION=1280x900
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
ENV DISPLAY=:0
EXPOSE 5900
EXPOSE 8083
EXPOSE 8080
VOLUME ["/cfg"]
CMD ["/usr/bin/supervisord"]
#docker run -d --name firefox -p 8083:8083 -p 5900:5900 -p 4444:4444 oldiy/chrome-novnc:latest
#docker run -d -p 8083:8083 -p 5900:5900 -p 8080:8080 demo
#docker run -d -p 28083:8083 -p 25900:5900 -p 28080:8080 novnc
#docker run -d -p 5900:5900 -p 5800:5800 docker.io/raykuo/chrome-jdownloader2
#docker run -e LANG=zh_CN -e LANGUAGE=zh_CN -e LC_ALL=zh_CN -p 5900:5900 -p 5800:5800 docker.io/raykuo/chrome-jdownloader2
#/usr/bin/x11vnc -nobell
#/usr/local/bin/x11vnc
#/usr/bin/x11vnc
#docker run -d -p 8083:8083 -p 5900:5900 oldiy/chrome-novnc:latest
server {
# ===> 监听端口
listen 8080;
# 编码
charset utf-8;
# ===> 域名可以有多个,用空格隔开,本地测试先修改/etc/hosts
server_name _;
# 开启gzip压缩输出
gzip on;
# 定义本虚拟主机的访问日志
access_log /var/log/nginx/api_access.log combined buffer=1k;
error_log /var/log/nginx/api_error.log info;
location = /favicon.ico {
log_not_found off;
access_log off;
}
# 防爬
location /robots.txt {
return 200 'User-agent: *\nDisallow: /';
}
# 日志
location ~/logs/.*\.(log|txt|zip)$ {
expires 1d;
root /;
break;
}
# 对 / 访问进行控制
location /token {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Referer $http_referer;
proxy_set_header Host $http_host;
proxy_buffers 256 4k;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
access_by_lua '
local cache_ngx = ngx.shared.my_cache
local token = ngx.var.cookie_token
if not token then
ngx.status = ngx.HTTP_FORBIDDEN
ngx.say(token)
ngx.exit(200)
end
local token2 = cache_ngx:get(token)
if not token2 then
local errs = "requests check fail"
ngx.status = ngx.HTTP_FORBIDDEN
ngx.say(errs)
ngx.exit(200)
end
return
';
proxy_pass http://127.0.0.1:8000;
proxy_set_header Referer $http_referer;
proxy_set_header Host $http_host;
proxy_buffers 256 4k;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 10086;
charset utf-8;
server_name _;
gzip on;
location /set {
access_by_lua '
local cache_ngx = ngx.shared.my_cache
local args = ngx.req.get_headers();
local token1 = args["appid"];
cache_ngx:set(token1, token1, 30 * 60)
local msg = "ok"
ngx.say(msg)
ngx.exit(200)
return
';
}
location /check {
access_by_lua '
local cache_ngx = ngx.shared.my_cache
local args = ngx.req.get_headers();
local token1 = args["appid"];
local token2 = cache_ngx:get(token1)
local errs = "oh,Only token1 Request will be Processe"
if not token1 then
ngx.status = ngx.HTTP_FORBIDDEN
ngx.say(errs)
ngx.exit(200)
end
local errs = "oh,Only token2 Request will be Processe"
if not token2 then
ngx.status = ngx.HTTP_FORBIDDEN
ngx.say(errs)
ngx.exit(200)
end
local errs = "oh,Only token Request will be Processe"
if token1 ~= token2 then
ngx.status = ngx.HTTP_FORBIDDEN
ngx.say(errs)
ngx.exit(200)
else
return
end
';
}
}
# coding: utf-8
import base64
import builtins
import hashlib
import json
import logging
import os
import random
import time
import requests
from flask import Flask, request
import rsa
from pyDes import des, CBC, PAD_PKCS5
import binascii
from selenium import webdriver
# import cv2
logging.basicConfig(level=logging.DEBUG)
app = Flask(__name__)
with open('public.pem', 'r') as f:
pubkey_s = f.read()
pubkey = rsa.PublicKey.load_pkcs1(pubkey_s.encode())
FATEA_PRED_URL = "http://pred.fateadm.com"
class TmpObj():
def __init__(self):
self.init = True
self.value = None
class Rsp():
def __init__(self):
self.ret_code = -1
self.cust_val = 0.0
self.err_msg = "succ"
self.pred_rsp = TmpObj()
def ParseJsonRsp(self, rsp_data):
if rsp_data is None:
self.err_msg = "http request failed, get rsp Nil data"
return
jrsp = json.loads(rsp_data)
self.ret_code = int(jrsp.get("RetCode", -1))
# {"RetCode":"0","ErrMsg":"","RequestId":"20200427130030715a606a00050d8cb0","RspData":"{\\"result\\": \\"gp5p\\"}"}
self.err_msg = jrsp.get("ErrMsg", 'succ')
self.request_id = jrsp.get("RequestId", '')
if self.ret_code == 0:
rslt_data = jrsp.get('RspData', "{}")
if rslt_data is not None and rslt_data != "":
jrsp_ext = json.loads(rslt_data)
if 'cust_val' in jrsp_ext.keys():
data = jrsp_ext["cust_val"]
self.cust_val = float(data)
if 'result' in jrsp_ext.keys():
data = jrsp_ext["result"]
self.pred_rsp.value = data
def CalcSign(pd_id, passwd, timestamp):
md5 = hashlib.md5()
md5.update((timestamp + passwd).encode())
csign = md5.hexdigest()
md5 = hashlib.md5()
md5.update((pd_id + timestamp + csign).encode())
csign = md5.hexdigest()
return csign
def PostFile(url, data, img_data):
rsp = Rsp()
files = {'img_data': ('img_data', img_data)}
headers = {"User-Agent": "Mozilla/5.0"}
r = requests.post(url, data=data, files=files, headers=headers)
# '{"RetCode":"0","ErrMsg":"","RequestId":"20200427130030715a606a00050d8cb0","RspData":"{\\"result\\": \\"gp5p\\"}"}'
rsp.ParseJsonRsp(r.text)
return rsp
class FateadmApi():
# API接口调用类
# 参数(appID,appKey,pdID,pdKey)
def __init__(self, app_id, app_key, pd_id, pd_key):
self.app_id = app_id
if app_id is None:
self.app_id = ""
self.app_key = app_key
self.pd_id = pd_id
self.pd_key = pd_key
self.host = FATEA_PRED_URL
#
# 识别验证码
# 参数:pred_type:识别类型 img_data:图片的数据
# 返回值:
# rsp.ret_code:正常返回0
# rsp.request_id:唯一订单号
# rsp.pred_rsp.value:识别结果
# rsp.err_msg:异常时返回异常详情
#
def Predict(self, pred_type, img_data, src_url=''):
tm = str(int(time.time()))
sign = CalcSign(self.pd_id, self.pd_key, tm)
param = {
"user_id": self.pd_id,
"timestamp": tm,
"sign": sign,
"predict_type": pred_type,
"up_type": "mt",
"src_url": src_url
}
if self.app_id != "":
#
asign = CalcSign(self.app_id, self.app_key, tm)
param["appid"] = self.app_id
param["asign"] = asign
url = self.host + "/api/capreg"
files = img_data
rsp = PostFile(url, param, files)
if rsp.ret_code == 0:
logging.info("predict succ ret: {} request_id: {} pred: {} err: {}".format(rsp.ret_code, rsp.request_id,
rsp.pred_rsp.value, rsp.err_msg))
else:
logging.info("predict failed ret: {} err: {}".format(rsp.ret_code, rsp.err_msg))
if rsp.ret_code == 4003:
# lack of money
logging.info("cust_val <= 0 lack of money, please charge immediately")
return rsp
def PredictFromFile(self, pred_type, file_name, src_url=''):
with open(file_name, "rb") as f:
data = f.read()
return self.Predict(pred_type, data, src_url)
def des_descrypt(s, key=None):
"""
DES 解密
:param s: 加密后的字符串,16进制
:return: 解密后的字符串
"""
secret_key = key
iv = secret_key
k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
de = k.decrypt(binascii.a2b_hex(s), padmode=PAD_PKCS5)
return de
def md5(str):
m = hashlib.md5()
b = str.encode(encoding='utf-8')
m.update(b)
return m.hexdigest()
class Driver(object):
def __init__(self):
self._driver = self.create_driver()
@staticmethod
def create_driver():
options = webdriver.ChromeOptions()
options.add_argument("--no-sandbox")
options.add_experimental_option("excludeSwitches", ['enable-automation'])
# command_executor = "http://localhost:4444/wd/hub"
# driver = webdriver.Remote(command_executor, desired_capabilities=options.to_capabilities())
return webdriver.Chrome(options=options)
@property
def driver(self):
return self._driver
def get_driver():
"""
:rtype : Driver
"""
if 'driver' not in builtins.__dict__:
builtins.__dict__['driver'] = Driver()
return builtins.__dict__['driver']
def do_tool(k):
os.system("xdotool key {}".format(k))
time.sleep(random.randint(1, 10) * 0.1)
def crop_code(img_path):
img = cv2.pyrDown(cv2.imread(img_path, cv2.IMREAD_UNCHANGED))
img2 = cv2.imread(img_path)
ret, thresh = cv2.threshold(cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY), 127, 255, cv2.THRESH_BINARY)
contours, hier = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
x, y, w, h = cv2.boundingRect(c)
if w < 30 or h < 20 or w * h > 1000:
continue
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cropImg = img2[y * 2:(y + h) * 2, x * 2:(x + w) * 2]
cv2.imwrite("code.png", cropImg)
return "code.png"
def try_login(account, password):
# todo 多次登录处理
driver = get_driver().driver
driver.get("https://b.alipay.com/index2.htm")
[do_tool(k) for k in account]
do_tool("Tab")
[do_tool(k) for k in password]
do_tool("Tab")
do_tool("Tab")
do_tool("Tab")
do_tool("Shift+Tab")
do_tool("Shift+Tab")
driver.get_screenshot_as_file('screenshot.png')
path = crop_code("screenshot.png")
pd_id = "122334"
pd_key = "CvSAzmpNTCk953nPqrciORQ5LaMmwsSZ"
app_id = "322334"
app_key = "ZVZG1lpunkJrrGA0xPJJgfRHHa384ycQ"
# 初始化api接口
other_api = FateadmApi(app_id, app_key, pd_id, pd_key)
rsp = other_api.PredictFromFile("30400", "code.png", "demo")
code = rsp.pred_rsp.value
[do_tool(k) for k in code]
# do_tool("KP_Enter")
@app.route('/token', methods=['GET', 'POST'])
def token():
ip = request.form['ip']
timestamp = request.form['timestamp']
msg = request.form['msg']
user_agent = request.headers.environ['HTTP_USER_AGENT']
token = md5(user_agent + ip + str(timestamp))
crypto_email_text = base64.b64decode(msg)
if rsa.verify(token.encode(), crypto_email_text, pubkey) == 'SHA-1':
# 容器加载token
url = 'http://127.0.0.1:10086/set'
headers = {"appid": token}
resp = requests.get(url, headers=headers)
print(resp.text)
return 'ok'
return 'fail'
# 接收账户密码接口
@app.route('/login', methods=['GET', 'POST'])
def login_alipay():
try:
logging.info("demo")
body = request.form['body']
logging.info(body)
body = des_descrypt(base64.b64decode(body), md5(pubkey_s)[:8])
if not body:
return "fail"
body = json.loads(body.decode())
account = body.get("account", "")
password = body.get("password", "")
if account and password:
# try_login(account, password)
return 'ok'
return 'disenable param'
except:
import traceback
logging.error(traceback.format_exc())
# todo 定时任务检查
if __name__ == '__main__':
app.run(port=8000)
[supervisord]
nodaemon=true
[program:X11]
command=/usr/bin/Xvfb :0 -screen 0 1024x768x24
autorestart=true
[program:x11vnc]
command=/usr/local/bin/x11vnc
autorestart=true
[program:fluxbox]
command=/usr/bin/startfluxbox
autorestart=true
[program:novnc]
command=/root/noVNC/utils/launch.sh --vnc localhost:5900 --listen 8084
autorestart=true
#[program:chrome]
#command=bash -c 'sleep 5 && /usr/bin/google-chrome-stable --no-sandbox --lang=zh-CN'
#autorestart=true
[program:selenium]
command=/usr/bin/java -jar /root/selenium.jar
autorestart=true
[program:nginx]
command=/usr/bin/nginx
autorestart=true
[program:fcitx]
command=/usr/bin/fcitx
autorestart=true
\ No newline at end of file
server {
listen 8083;
charset utf-8;
server_name _;
gzip on;
location / {
access_by_lua '
local cache_ngx = ngx.shared.my_cache
local token = ngx.var.cookie_token
if not token then
ngx.status = ngx.HTTP_FORBIDDEN
ngx.say(token)
ngx.exit(200)
end
local token2 = cache_ngx:get(token)
if not token2 then
local errs = "requests check fail"
ngx.status = ngx.HTTP_FORBIDDEN
ngx.say(errs)
ngx.exit(200)
end
return
';
proxy_pass http://127.0.0.1:8084;
proxy_set_header Referer $http_referer;
proxy_set_header Host $http_host;
proxy_buffers 256 4k;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# coding: utf-8
import base64
import rsa
# # 生成密钥
# (pubkey, privkey) = rsa.newkeys(1024)
#
# # 保存密钥
# with open('public.pem', 'w+') as f:
# f.write(pubkey.save_pkcs1().decode())
#
# with open('private.pem', 'w+') as f:
# f.write(privkey.save_pkcs1().decode())
# 导入密钥
with open('public.pem', 'r') as f:
pubkey = rsa.PublicKey.load_pkcs1(f.read().encode())
with open('private.pem', 'r') as f:
privkey = rsa.PrivateKey.load_pkcs1(f.read().encode())
# # 明文:业务员发现的商机
# message = '这是商机:...'
#
# # 业务员用公司经理事先给的公钥对明文加密,得到密文
# crypto_email_text = rsa.encrypt(message.encode(), pubkey)
#
# # 然后,业务员用email发送密文
# # 。。。
#
#
# # email在网络传输中 。。。(各种数据被抓包、邮箱密码泄露)
# # 没办法,还是被有心人看到了这封email:
# print(crypto_email_text) # 什么鬼?看不懂啊!
#
# # 最后,公司经理也收到了业务员们发了的email。打开,也只看到一堆奇怪的字符!
# # 没问题,公司经理用自己的私钥对收到的密文进行解密,就可得到明文
# message = rsa.decrypt(crypto_email_text, privkey).decode()
#
# # 然后,就可以看到重要的商机信息了
# print(message)
#
# # =================================
# # 场景二:身份确认问题
# # 为了开拓市场,公司经理分派了一群业务员到各地考察商机。
# # 在这过程中,公司经理常常通过email向业务员下达重要指令
# # 然而,网络是及其不安全的!譬如:数据包被修改、邮箱密码泄露...
# # 商业竞争对手可以通过各种手段伪造/修改公司经理的重要指令!
# #
# # 话说这天早上,业务员照常打开邮箱,发现公司经理的一封email:命令他马上回国。
# # 不对啊。昨天说要在这边扩大业务,怎么今天就变了?
# # 这封email是公司经理本人发的吗?
# # 怎么办?
# #
# # 没错!聪明的您一定也想到了:签名。
# # =================================
#
# # 明文:公司经理的指令
message = '这是重要指令:...'
# 公司经理私钥签名
crypto_email_text = rsa.sign(message.encode(), privkey, 'SHA-1')
print(base64.b64encode(crypto_email_text).decode())
# 业务员同时收到指令明文、密文,然后用公钥验证,进行身份确认
print(rsa.verify(message.encode(), crypto_email_text, pubkey))
# coding: utf-8
import datetime
import json
import os
import time
import traceback
from concurrent.futures import ThreadPoolExecutor
import paramiko
import requests
from paramiko import AuthenticationException
from base.logger import log
executor = ThreadPoolExecutor(max_workers=100)
def between(source, begin_str, end_str, start=''):
start_index = source.find(start)
start_index = start_index + len(start) if start_index else 0
b = source.find(begin_str, start_index)
if end_str:
e = source.find(end_str, b + len(begin_str))
else:
e = len(source)
return source[b + len(begin_str):e]
def ssh_command(ssh, command):
stdin, stdout, stderr = ssh.exec_command(command)
res, err = stdout.read(), stderr.read()
result = res if res else err
return result.decode()
def create_ssh(ip, username, password):
transport = paramiko.Transport((ip, 22))
transport.connect(username=username, password=password)
# 创建SSH对象,SSHClient是定义怎么传输命令、怎么交互文件
ssh = paramiko.SSHClient()
ssh._transport = transport
return ssh
def create_sftpc(ip, username, password):
nimei = paramiko.Transport((ip, 22))
nimei.connect(username=username, password=password)
p = paramiko.SFTPClient.from_transport(nimei)
return p
def update_shop_info(site, webname, host_id=0, status=2, **kwargs):
url = "http://127.0.0.1:7190/plugin/shop_info"
data = {"domain": site, "name": webname, "host_id": host_id, "status": status}
if kwargs:
data = dict(data, **kwargs)
print(data)
# url = "http://172.30.10.204:7190/plugin/shop_info"
executor.submit(requests.put, url=url, json=data, )
def update_docker_info(ip, status, remark, **kwargs):
url = "http://127.0.0.1:7190/plugin/push_status"
data = {"module": "linux_docker", "unique_id": ip, "status": status, "remark": remark}
if kwargs:
data = dict(data, **kwargs)
print(data)
# url = "http://172.30.10.204:7190/plugin/shop_info"
executor.submit(requests.post, url=url, json=data, )
def install_shop(ssh, site, ip, webname, sub=""):
return ssh_command(ssh, "sh /root/install-shop_build.sh {} {} {} '{}'".format(site, ip, webname, sub))
def install_bt(ssh):
return ssh_command(ssh, "sh /root/install-bt.sh")
def get_bt_info(ssh):
result = ssh_command(ssh, "/etc/init.d/bt default")
url = between(result, "Bt-Panel-URL: ", "username").replace("username", "").strip()
username = between(result, "username: ", "password").replace("password", "").strip()
password = between(result, "password: ", "Warning")[:8]
return url, username, password
def push_file(ip, username, password, push, local_dir=''):
p = create_sftpc(ip, username, password)
path = "{}/{}".format(os.path.dirname(__file__), local_dir)
try:
for local_path, remote_path in push.items():
p.put(local_path.format(path=path), remote_path) # 上传文件到远程机
finally:
p.close()
def auto_install_bt(ip, username, password, site, webname, sub="", host_id=0):
transport = paramiko.Transport((ip, 22))
try:
print('****************************开始操作:{}************************'.format(
datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
push_file(ip, username, password,
{"{path}/install-bt.sh": '/root/install-bt.sh',
"{path}/auto_install.py": "/root/auto_install.py",
"{path}/install-shop_build.sh": "/root/install-shop_build.sh"}, "shop_build")
transport.connect(username=username, password=password)
# 创建SSH对象,SSHClient是定义怎么传输命令、怎么交互文件
ssh = paramiko.SSHClient()
ssh._transport = transport
# 执行命令,不要执行top之类的在不停的刷新的命令
webname = webname or "DBShop电子商务系统"
update_shop_info(site, webname, remark="开始下载宝塔")
result = install_bt(ssh)
url, username, password = get_bt_info(ssh)
update_shop_info(site, webname, remark="宝塔初始化完成", url=url, username=username, password=password)
print("install_bt", result)
i = 0
for i in range(2):
result = install_shop(ssh, site, ip, webname, sub)
if "商城已存在" in result:
print("商城已存在")
update_shop_info(site, webname, host_id, status=1, remark="商城已存在", url=url, username=username,
password=password)
break
ssl_remark, ssl_status = "", 0
if "配置商城成功" in result or "商城已存在" in result:
print("更新商城信息")
if "申请HTTPS证书成功" in result or "HTTPS证书已存在" in result:
ssl_remark = "申请HTTPS证书成功"
ssl_status = 1
elif "申请HTTPS证书失败" in result:
ssl_remark = between(result, "new ssl response:[", "]\n")
ssl_remark = json.loads(ssl_remark)['msg'][0]
ssl_status = 2
update_shop_info(site, webname, host_id, status=1, remark="商城部署成功", url=url, username=username,
password=password, ssl_remark=ssl_remark, ssl_status=ssl_status)
else:
update_shop_info(site, webname, host_id, status=3, remark="商城部署失败,请重试", url=url, username=username,
password=password)
except AuthenticationException as err:
update_shop_info(site, webname, host_id, status=3, remark="连接失败,请检查远程信息")
except Exception as err:
print(traceback.format_exc())
update_shop_info(site, webname, host_id, status=3, remark="商城部署异常,请重试")
finally:
# 关闭服务器连接
transport.close()
def install_docker(ip, username, password, pubkey):
ssh = create_ssh(ip, username, password)
# todo yum 正在允许会阻塞进程
log.i()
res = ssh_command(ssh, "yum install -y docker")
if "Complete!" in res or "already installed" in res or '完毕!' in res:
update_docker_info(ip, 0, "完成 docker 安装")
res = ssh_command(ssh, "systemctl start docker")
if not res:
update_docker_info(ip, 0, "启动 docker 服务")
# 生成 rsa 公私钥,保存
res = ssh_command(ssh, "mkdir -p /root/build")
temp_file = '/tmp/{}.pem'.format(int(time.time()))
with open(temp_file.format(ip), 'w+') as f:
f.write(pubkey)
push_file(ip, username, password,
{"{path}/api-redir.conf": '/root/build/api-redir.conf',
"{path}/cache-redir.conf": "/root/build/cache-redir.conf",
"{path}/vnc-redir.conf": "/root/build/vnc-redir.conf",
"{path}/Dockerfile": "/root/build/Dockerfile",
"{path}/supervisord.conf": "/root/build/supervisord.conf",
"{path}/small_web.py": "/root/build/small_web.py",
temp_file: "/root/build/public.pem"}, "docker_build")
if __name__ == '__main__':
# install_docker("42.194.224.77", "root", "!QAZ2wsx#EDCbnm")
install_docker("172.30.20.120", "root", "rootroot", """-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAIr7A14mHoube/cO318GjcTHhSGuSGPfrPf3+2+6sx9dKgCpTAyhpMaD
kTMhDd911ShIY4useo95zGV98uD6FLKfINvJ2YdKNRLCod1aVBfb3fxMr4RFn/Ci
GCYRU9XdjW5Xj30oOkbFc6t3Q1AmS0vmZ0e1+H1D9U5ha7Qap8eZAgMBAAE=
-----END RSA PUBLIC KEY-----""")
# coding: utf-8
import re
import socket
import requests
import binascii
import hashlib
import rsa
from pyDes import des, CBC, PAD_PKCS5
def current_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
def network_ip():
req = requests.get("http://txt.go.sohu.com/ip/soip")
ip = re.findall(r'\d+.\d+.\d+.\d+', req.text)[0]
return ip
# 秘钥
KEY = 'mHAxsLYz'
def des_encrypt(s, key=None):
"""
DES 加密
:param s: 原始字符串
:return: 加密后字符串,16进制
"""
secret_key = key or KEY
iv = secret_key
k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
en = k.encrypt(s, padmode=PAD_PKCS5)
return binascii.b2a_hex(en)
def des_descrypt(s, key=None):
"""
DES 解密
:param s: 加密后的字符串,16进制
:return: 解密后的字符串
"""
secret_key = key or KEY
iv = secret_key
k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
de = k.decrypt(binascii.a2b_hex(s), padmode=PAD_PKCS5)
return de
def md5(str):
m = hashlib.md5()
b = str.encode(encoding='utf-8')
m.update(b)
return m.hexdigest()
def is_domain(domain):
domain_regex = re.compile(
r'(?:[A-Z0-9_](?:[A-Z0-9-_]{0,247}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))\Z',
re.IGNORECASE)
return True if domain_regex.match(domain) else False
def is_ipv4(address):
ipv4_regex = re.compile(
r'(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}',
re.IGNORECASE)
return True if ipv4_regex.match(address) else False
def new_keys():
(pubkey, privkey) = rsa.newkeys(1024)
return pubkey.save_pkcs1().decode(), privkey.save_pkcs1().decode()
if __name__ == '__main__':
print(new_keys())
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment