pull/632/head
Mr Chen 6 months ago
parent ed375271d2
commit 5b2e40404d
  1. 3
      web/admin/site/ssl_acme.py
  2. 55
      web/admin/site/ssl_let.py
  3. 178
      web/core/mw.py
  4. 248
      web/static/app/site.js
  5. 297
      web/utils/site.py

@ -42,9 +42,10 @@ def create_acme():
force = request.form.get('force', '')
renew = request.form.get('renew', '')
email = request.form.get('email', '')
wildcard_domain = request.form.get('wildcard_domain','')
apply_type = request.form.get('apply_type', 'file')
dnspai = request.form.get('dnspai','')
return MwSites.instance().createAcme(site_name, domains,force,renew,apply_type,dnspai, email)
return MwSites.instance().createAcme(site_name, domains, force, renew, apply_type, dnspai, email, wildcard_domain)

@ -0,0 +1,55 @@
# coding:utf-8
# ---------------------------------------------------------------------------------
# MW-Linux面板
# ---------------------------------------------------------------------------------
# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved.
# ---------------------------------------------------------------------------------
# Author: midoks <midoks@163.com>
# ---------------------------------------------------------------------------------
import os
import json
from flask import Blueprint, render_template
from flask import request
from admin.user_login_check import panel_login_required
from utils.plugin import plugin as MwPlugin
from utils.site import sites as MwSites
import core.mw as mw
import thisdb
from .site import blueprint
# 获取ACME日志
@blueprint.route('/get_let_logs', endpoint='get_let_logs', methods=['POST'])
@panel_login_required
def get_let_logs():
log_file = MwSites.instance().letLogFile()
if not os.path.exists(log_file):
mw.execShell('touch ' + log_file)
return mw.returnData(True, 'OK', log_file)
@blueprint.route('/create_let', endpoint='create_let', methods=['POST'])
@panel_login_required
def create_let():
site_name = request.form.get('siteName', '')
domains = request.form.get('domains', '')
force = request.form.get('force', '')
renew = request.form.get('renew', '')
email = request.form.get('email', '')
wildcard_domain = request.form.get('wildcard_domain','')
apply_type = request.form.get('apply_type', 'file')
dnspai = request.form.get('dnspai','')
return MwSites.instance().createLet(site_name, domains, force, renew, apply_type, dnspai, email, wildcard_domain)

@ -836,6 +836,166 @@ def deDoubleCrypt(key, strings):
writeFileLog(getTracebackInfo())
return strings
def aesEncrypt(data, key='ABCDEFGHIJKLMNOP', vi='0102030405060708'):
# aes加密
# @param data 被加密的数据
# @param key 加解密密匙 16位
# @param vi 16位
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
if not isinstance(data, bytes):
data = data.encode()
# AES_CBC_KEY = os.urandom(32)
# AES_CBC_IV = os.urandom(16)
AES_CBC_KEY = key.encode()
AES_CBC_IV = vi.encode()
# print("AES_CBC_KEY:", AES_CBC_KEY)
# print("AES_CBC_IV:", AES_CBC_IV)
padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_data = padder.update(data) + padder.finalize()
cipher = Cipher(algorithms.AES(AES_CBC_KEY),
modes.CBC(AES_CBC_IV),
backend=default_backend())
encryptor = cipher.encryptor()
edata = encryptor.update(padded_data)
# print(edata)
# print(str(edata))
# print(edata.decode())
return edata
def aesDecrypt(data, key='ABCDEFGHIJKLMNOP', vi='0102030405060708'):
# aes加密
# @param data 被解密的数据
# @param key 加解密密匙 16位
# @param vi 16位
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
if not isinstance(data, bytes):
data = data.encode()
AES_CBC_KEY = key.encode()
AES_CBC_IV = vi.encode()
cipher = Cipher(algorithms.AES(AES_CBC_KEY),
modes.CBC(AES_CBC_IV),
backend=default_backend())
decryptor = cipher.decryptor()
ddata = decryptor.update(data)
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
data = unpadder.update(ddata)
try:
uppadded_data = data + unpadder.finalize()
except ValueError:
raise Exception('无效的加密信息!')
return uppadded_data
def aesEncrypt_Crypto(data, key, vi):
# 该方法保留,暂时不使用
# aes加密
# @param data 被加密的数据
# @param key 加解密密匙 16位
# @param vi 16位
from Crypto.Cipher import AES
cryptor = AES.new(key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8'))
# 判断是否含有中文
zhmodel = re.compile(u'[\u4e00-\u9fff]')
match = zhmodel.search(data)
if match == None:
# 无中文时
add = 16 - len(data) % 16
pad = lambda s: s + add * chr(add)
data = pad(data)
enctext = cryptor.encrypt(data.encode('utf8'))
else:
# 含有中文时
data = data.encode()
add = 16 - len(data) % 16
data = data + add * (chr(add)).encode()
enctext = cryptor.encrypt(data)
encodestrs = base64.b64encode(enctext).decode('utf8')
return encodestrs
def aesDecrypt_Crypto(data, key, vi):
# 该方法保留,暂时不使用
# aes加密
# @param data 被加密的数据
# @param key 加解密密匙 16位
# @param vi 16位
from crypto.Cipher import AES
data = data.encode('utf8')
encodebytes = base64.urlsafe_b64decode(data)
cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8'))
text_decrypted = cipher.decrypt(encodebytes)
# 判断是否含有中文
zhmodel = re.compile(u'[\u4e00-\u9fff]')
match = zhmodel.search(text_decrypted)
if match == False:
# 无中文时补位
unpad = lambda s: s[0:-s[-1]]
text_decrypted = unpad(text_decrypted)
text_decrypted = text_decrypted.decode('utf8').rstrip() # 去掉补位的右侧空格
return text_decrypted
def getDefault(data,val,def_val=''):
if val in data:
return data[val]
return def_val
def encodeImage(imgsrc, newsrc):
# 图片加密
import struct
old_fp = open(imgsrc, 'rb')
imgFile = old_fp.read()
old_fp.close()
new_fp = open(newsrc,"wb")
for x in imgFile:
value = x ^ 86
value = hex(value)
s = struct.pack('B',int(value,16))
new_fp.write(s)
new_fp.close()
return True
def buildSoftLink(src, dst, force=False):
'''
建立软连接
'''
if not os.path.exists(src):
return False
if os.path.exists(dst) and force:
os.remove(dst)
if not os.path.exists(dst):
execShell('ln -sf "' + src + '" "' + dst + '"')
return True
return False
# ------------------------------ network start -----------------------------
def HttpGet(url, timeout=10):
@ -1009,6 +1169,14 @@ def panelCmd(method):
# ------------------------------ openresty start -----------------------------
def getOpVer():
version = ''
version_file_pl = getServerDir() + '/openresty/version.pl'
if os.path.exists(version_file_pl):
version = readFile(version_file_pl)
version = version.strip()
return version
def checkWebConfig():
op_dir = getServerDir() + '/openresty/nginx'
# "ulimit -n 10240 && " +
@ -1179,6 +1347,10 @@ def getMyORM():
# ---------------------------------------------------------------------------------
##################### ssl start #########################################
def strfDate(sdate):
return time.strftime('%Y-%m-%d', time.strptime(sdate, '%Y%m%d%H%M%S'))
# 获取证书名称
def getCertName(certPath):
if not os.path.exists(certPath):
@ -1205,11 +1377,9 @@ def getCertName(certPath):
if hasattr(issuer, 'O'):
result['issuer'] = issuer.O
# 取到期时间
result['notAfter'] = strfDate(
bytes.decode(x509.get_notAfter())[:-1])
result['notAfter'] = strfDate(bytes.decode(x509.get_notAfter())[:-1])
# 取申请时间
result['notBefore'] = strfDate(
bytes.decode(x509.get_notBefore())[:-1])
result['notBefore'] = strfDate(bytes.decode(x509.get_notBefore())[:-1])
# 取可选名称
result['dns'] = []
for i in range(x509.get_extension_count()):

@ -2119,13 +2119,87 @@ function renderDnsapi(){
}
});
$('#dnsapi_set').on('click', function(){
$('#dnsapi_set').unbind('click').on('click', function(){
var index = $('#dnsapi_option option:selected').attr('index');
renderDnsapiHtml(data[index]);
});
},'json');
}
function opSSLNow(type, id, siteName, callback){
var now = '<div class="myKeyCon ptb15">\
<div class="ssl_state_info" style="display:none;"></div>\
<div class="custom_certificate_info">\
<div class="ssl-con-key pull-left mr20">密钥(KEY)<br><textarea id="key" class="bt-input-text"></textarea></div>\
<div class="ssl-con-key pull-left">证书(PEM格式)<br><textarea id="csr" class="bt-input-text"></textarea></div>\
</div>\
<div class="ssl-btn pull-left mtb15" style="width:100%">\
<button class="btn btn-success btn-sm" onclick="saveSSL(\''+siteName+'\')">保存</button>\
</div>\
</div>\
<ul class="help-info-text c7 pull-left">\
<li>粘贴您的*.key以及*.pem内容然后保存即可</li>\
<li>如果浏览器提示证书链不完整,请检查是否正确拼接PEM证书</li><li>PEM = .crt + (root_bundle).crt</li>\
<li>在未指定SSL默认站点时,未开启SSL的站点使用HTTPS会直接访问到已开启SSL的站点</li>\
</ul>';
$(".tab-con").html(now);
var key = '';
var csr = '';
var loadT = layer.msg('正在提交任务...',{icon:16,time:0,shade: [0.3, '#000']});
$.post('/site/get_ssl','site_name='+siteName,function(data){
layer.close(loadT);
var rdata = data['data'];
if (rdata['cert_data']){
var issuer = rdata['cert_data']['issuer'].split(" ");
var domains = rdata['cert_data']['dns'].join("、");
var cert_data = "<div class='state_info_flex'>\
<div class='state_item'><span>证书品牌</span><span class='ellipsis_text'>"+issuer[0]+"</span></div>\
<div class='state_item'><span>到期时间</span><span class='btlink'>"+rdata['cert_data']['endtime']+"</span></div>\
</div>\
<div class='state_info_flex'>\
<div class='state_item'><span>认证域名</span><span class='ellipsis_text'>"+domains+"</span></div>\
<div class='state_item'><span>强制HTTPS</span><span class='switch'>\
<input class='btswitch btswitch-ios' id='toHttps' type='checkbox'>\
<label class='btswitch-btn' for='toHttps' onclick=\"httpToHttps('" + siteName + "')\">\
</span></div>\
</div>";
$(".ssl_state_info").html(cert_data);
$(".ssl_state_info").css('display','block');
}
if(rdata.key == false){
rdata.key = '';
} else {
$(".ssl-btn").append('<button style=\'margin-left:3px;\' class="btn btn-success btn-sm" onclick="deleteSSL(\'now\','+id+',\''+siteName+'\')">删除</button>');
}
if(rdata.csr == false){
rdata.csr = '';
}
$("#key").val(rdata.key);
$("#csr").val(rdata.csr);
$("#toHttps").attr('checked',rdata.httpTohttps);
if(rdata.status){
$('.warning_info').css('display','none');
$(".ssl-btn").append("<button class='btn btn-success btn-sm' onclick=\"ocSSL('close_ssl_conf','"+siteName+"')\" style='margin-left:3px;'>关闭SSL</button>");
$('#now_ssl').html('当前证书 - <i style="color:#20a53a;">[已部署SSL]</i>');
} else{
$('.warning_info').css('display','block');
$('#now_ssl').html('当前证书 - <i style="color:red;">[未部署SSL]</i>');
}
if (typeof (callback) != 'undefined'){
callback(rdata);
}
},'json');
}
function opSSLAcme(type, id, siteName, callback){
var acme = '<div class="apply_ssl">\
<div class="label-input-group">\
@ -2192,7 +2266,6 @@ function opSSLAcme(type, id, siteName, callback){
}
});
renderDnsapi();
$.post('/site/get_ssl','site_name='+siteName+'&ssl_type=acme', function(data){
@ -2278,92 +2351,33 @@ function opSSLAcme(type, id, siteName, callback){
},'json');
}
function opSSLNow(type, id, siteName, callback){
var now = '<div class="myKeyCon ptb15">\
<div class="ssl_state_info" style="display:none;"></div>\
<div class="custom_certificate_info">\
<div class="ssl-con-key pull-left mr20">密钥(KEY)<br><textarea id="key" class="bt-input-text"></textarea></div>\
<div class="ssl-con-key pull-left">证书(PEM格式)<br><textarea id="csr" class="bt-input-text"></textarea></div>\
</div>\
<div class="ssl-btn pull-left mtb15" style="width:100%">\
<button class="btn btn-success btn-sm" onclick="saveSSL(\''+siteName+'\')">保存</button>\
</div>\
</div>\
<ul class="help-info-text c7 pull-left">\
<li>粘贴您的*.key以及*.pem内容然后保存即可</li>\
<li>如果浏览器提示证书链不完整,请检查是否正确拼接PEM证书</li><li>PEM = .crt + (root_bundle).crt</li>\
<li>在未指定SSL默认站点时,未开启SSL的站点使用HTTPS会直接访问到已开启SSL的站点</li>\
</ul>';
$(".tab-con").html(now);
var key = '';
var csr = '';
var loadT = layer.msg('正在提交任务...',{icon:16,time:0,shade: [0.3, '#000']});
$.post('/site/get_ssl','site_name='+siteName,function(data){
layer.close(loadT);
var rdata = data['data'];
if (rdata['cert_data']){
var issuer = rdata['cert_data']['issuer'].split(" ");
var domains = rdata['cert_data']['dns'].join("、");
var cert_data = "<div class='state_info_flex'>\
<div class='state_item'><span>证书品牌</span><span class='ellipsis_text'>"+issuer[0]+"</span></div>\
<div class='state_item'><span>到期时间</span><span class='btlink'>"+rdata['cert_data']['endtime']+"</span></div>\
</div>\
<div class='state_info_flex'>\
<div class='state_item'><span>认证域名</span><span class='ellipsis_text'>"+domains+"</span></div>\
<div class='state_item'><span>强制HTTPS</span><span class='switch'>\
<input class='btswitch btswitch-ios' id='toHttps' type='checkbox'>\
<label class='btswitch-btn' for='toHttps' onclick=\"httpToHttps('" + siteName + "')\">\
</span></div>\
</div>";
$(".ssl_state_info").html(cert_data);
$(".ssl_state_info").css('display','block');
}
if(rdata.key == false){
rdata.key = '';
} else {
$(".ssl-btn").append('<button style=\'margin-left:3px;\' class="btn btn-success btn-sm" onclick="deleteSSL(\'now\','+id+',\''+siteName+'\')">删除</button>');
}
if(rdata.csr == false){
rdata.csr = '';
}
$("#key").val(rdata.key);
$("#csr").val(rdata.csr);
$("#toHttps").attr('checked',rdata.httpTohttps);
if(rdata.status){
$('.warning_info').css('display','none');
$(".ssl-btn").append("<button class='btn btn-success btn-sm' onclick=\"ocSSL('close_ssl_conf','"+siteName+"')\" style='margin-left:3px;'>关闭SSL</button>");
$('#now_ssl').html('当前证书 - <i style="color:#20a53a;">[已部署SSL]</i>');
} else{
$('.warning_info').css('display','block');
$('#now_ssl').html('当前证书 - <i style="color:red;">[未部署SSL]</i>');
}
if (typeof (callback) != 'undefined'){
callback(rdata);
}
},'json');
}
function opSSLLet(type, id, siteName, callback){
var lets = '<div class="apply_ssl">\
<div class="label-input-group">\
<div class="line mtb10">\
<form>\
<span class="tname text-center">验证方式</span>\
<div style="margin-top:7px;display:inline-block">\
<input type="radio" name="c_type" onclick="fileCheck()" id="check_file" checked="checked" />\
<label class="mr20" for="check_file" style="font-weight:normal">文件验证</label></label>\
</div>\
</form>\
<span class="tname text-center">验证方式</span>\
<div style="margin-top:7px;display:inline-block">\
<input type="radio" name="apply_type" value="file" id="check_file" checked="checked"/>\
<label class="mr20" for="check_file" style="font-weight:normal">文件验证</label></label>\
<input type="radio" name="apply_type" value="dns" id="check_dns"/>\
<label class="mr20" for="check_dns" style="font-weight:normal">DNS验证</label></label>\
</div>\
</div>\
<div class="line mtb10" id="dnsapi_option" style="display:none;">\
<span class="tname text-center" style="line-height: 42px;">选择DNS接口</span>\
<div style="margin-top:7px;display:inline-block">\
<select name="dnspai" class="bt-input-text mr20">\
<option value="none">手动解析</option>\
</select>\
<button id="dnsapi_set" class="btn btn-default btn-sm btn-title" style="display:none;">配置</button>\
</div>\
</div>\
<div class="check_message line" id="wildcard_domain_block" style="display:none;">\
<div style="margin-left:100px">\
<input type="checkbox" name="wildcard_domain" id="wildcard_domain" checked="checked">\
<label class="mr20" for="wildcard_domain" style="font-weight:normal">自动组合泛域名</label>\
</div>\
</div>\
<div class="check_message line">\
<div style="margin-left:100px">\
<input type="checkbox" name="checkDomain" id="checkDomain" checked="">\
@ -2391,6 +2405,20 @@ function opSSLLet(type, id, siteName, callback){
</div>';
$(".tab-con").html(lets);
$('input[name="apply_type"]').on('change', function(){
var val = $(this).val();
if (val == 'file'){
$('#dnsapi_option').css('display','none');
$('#wildcard_domain_block').css('display','none');
} else {
$('#dnsapi_option').css('display','block');
$('#wildcard_domain_block').css('display','block');
}
});
renderDnsapi();
$.post('/site/get_ssl', 'site_name='+siteName+'&ssl_type=lets', function(data){
var rdata = data['data'];
if(rdata.csr == false){
@ -2517,46 +2545,58 @@ function ocSSL(action,siteName){
//生成SSL
function newSSL(siteName, id, domains){
showSpeedWindow('正在申请...', 'site.get_let_logs', function(layers,index){
var force = '';
if ($("#checkDomain").prop("checked")){
force = '&force=true';
}
var email = $("input[name='admin_email']").val();
$.post('/site/create_let','siteName='+siteName+'&domains='+domains+'&email='+email + force,function(rdata){
layer.close(index);
if(rdata.status){
showMsg(rdata.msg, function(){
var pdata = {};
pdata['siteName'] = siteName;
pdata['domains'] = domains;
pdata['email'] = $("input[name='admin_email']").val();
if($("#checkDomain").prop("checked")){
pdata['force'] = 'true';
}
if($("#wildcard_domain").prop("checked")){
pdata['wildcard_domain'] = 'true';
}
var apply_type = $('input[name="apply_type"]:checked').val();
pdata['apply_type'] = apply_type;
if (apply_type == 'dns'){
pdata['dnspai'] = $('#dnsapi_option option:selected').val();
}
$.post('/site/create_let',pdata,function(rdata){
showMsg(rdata.msg, function(){
layer.close(index);
if(rdata.status){
$(".tab-nav span:first-child").click();
},{icon:1}, 2000);
return;
}
layer.msg(rdata.msg,{icon:2,area:'500px',time:0,shade:0.3,shadeClose:true});
}
},{icon:rdata.status?1:2}, 3000);
},'json');
});
}
function newAcmeSSL(siteName, id, domains){
showSpeedWindow('正在由ACME申请...', 'site.get_acme_logs', function(layers,index){
var force = '';
var email = $("input[name='admin_email']").val();
var apply_type = $('input[name="apply_type"]:checked').val();
var pdata = {};
pdata['siteName'] = siteName;
pdata['domains'] = domains;
pdata['email'] = email;
pdata['apply_type'] = apply_type;
pdata['email'] = $("input[name='admin_email']").val();
if($("#checkDomain").prop("checked")){
pdata['force'] = 'true';
}
if($("#wildcard_domain").prop("checked")){
pdata['wildcard_domain'] = 'true';
}
var apply_type = $('input[name="apply_type"]:checked').val();
pdata['apply_type'] = apply_type;
if (apply_type == 'dns'){
pdata['dnspai'] = $('#dnsapi_option option:selected').val();
}
console.log(pdata);
$.post('/site/create_acme',pdata,function(rdata){
console.log(rdata);
showMsg(rdata.msg, function(){
layer.close(index);
if(rdata.status){

@ -419,6 +419,74 @@ class sites(object):
mw.writeLog('网站管理', '设置成功,站点【{1}】到期【{2}】后将自动停止!', (info['name'], end_date,))
return mw.returnData(True, '设置成功,站点到期后将自动停止!')
# ssl相关方法 start
def setSslConf(self, site_name):
file = self.getHostConf(site_name)
conf = mw.readFile(file)
version = mw.getOpVer()
keyPath = self.sslDir + '/' + site_name + '/privkey.pem'
certPath = self.sslDir + '/' + site_name + '/fullchain.pem'
if conf:
if conf.find('ssl_certificate') == -1:
# ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
# add_header Alt-Svc 'h3=":443";ma=86400,h3-29=":443";ma=86400';
http3Header = """
add_header Strict-Transport-Security "max-age=63072000";
add_header Alt-Svc 'h3=":443";ma=86400';
"""
if not version.startswith('1.25'):
http3Header = '';
sslStr = """#error_page 404/404.html;
ssl_certificate %s;
ssl_certificate_key %s;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
%s
error_page 497 https://$host$request_uri;""" % (certPath, keyPath, http3Header)
if(conf.find('ssl_certificate') != -1):
return mw.returnData(True, 'SSL开启成功!')
conf = conf.replace('#error_page 404/404.html;', sslStr)
rep = r"listen\s+([0-9]+)\s*[default_server|reuseport]*;"
tmp = re.findall(rep, conf)
if not mw.inArray(tmp, '443'):
listen = re.search(rep, conf).group()
if version.startswith('1.25'):
http_ssl = "\n\tlisten 443 ssl;"
http_ssl = http_ssl + "\n\tlisten [::]:443 ssl;"
http_ssl = http_ssl + "\n\tlisten 443 quic;"
http_ssl = http_ssl + "\n\tlisten [::]:443 quic;"
http_ssl = http_ssl + "\n\thttp2 on;"
else:
http_ssl = "\n\tlisten 443 ssl http2;"
http_ssl = http_ssl + "\n\tlisten [::]:443 ssl http2;"
conf = conf.replace(listen, listen + http_ssl)
mw.backFile(file)
mw.writeFile(file, conf)
isError = mw.checkWebConfig()
if(isError != True):
mw.restoreFile(file)
return mw.returnData(False, '证书错误: <br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>')
self.saveCert(keyPath, certPath)
msg = mw.getInfo('网站[{1}]开启SSL成功!', (site_name,))
mw.writeLog('网站管理', msg)
mw.restartWeb()
return mw.returnData(True, 'SSL开启成功!')
# 设置网站备注
def setPs(self, site_id, ps):
if thisdb.setSitesData(site_id, ps=ps):
@ -1466,6 +1534,7 @@ location ^~ {from} {\n\
csr = mw.readFile(csr_path)
cert_data = mw.getCertName(csr_path)
# print(csr_path,cert_data)
data = {
'status': status,
'domain': domains,
@ -1477,6 +1546,21 @@ location ^~ {from} {\n\
}
return mw.returnData(True, 'OK', data)
def saveCert(self, keyPath, certPath):
try:
certInfo = mw.getCertName(certPath)
if not certInfo:
return mw.returnData(False, '证书解析失败!')
vpath = self.sslDir + '/' + certInfo['subject'].strip()
if not os.path.exists(vpath):
os.system('mkdir -p ' + vpath)
mw.writeFile(vpath + '/privkey.pem', mw.readFile(keyPath))
mw.writeFile(vpath + '/fullchain.pem', mw.readFile(certPath))
mw.writeFile(vpath + '/info.json', json.dumps(certInfo))
return mw.returnData(True, '证书保存成功!')
except Exception as e:
return mw.returnData(False, '证书保存失败!')
def getCertList(self):
try:
vpath = self.sslDir
@ -1657,7 +1741,6 @@ location ^~ {from} {\n\
thisdb.setOption('dnsapi',json.dumps(dnsapi_data))
return mw.returnData(True, '设置成功!')
def acmeLogFile(self):
return mw.getPanelDir() + '/logs/acme.log'
@ -1666,13 +1749,20 @@ location ^~ {from} {\n\
mw.writeFile(log_file, msg+"\n", "wb+")
return True
def letLogFile(self):
return mw.getPanelDir() + '/logs/letsencrypt.log'
def writeLetLog(self,msg):
log_file = self.letLogFile()
mw.writeFile(log_file, msg+"\n", "wb+")
return True
def createAcmeMultiDomin(self):
pass
def createAcmeFile(self, site_name, domains,force,renew, apply_type, dnspai, email):
def createAcmeFile(self, site_name, domains, email, force, renew):
print(site_name, domains,force,renew,apply_type, dnspai, email)
print(site_name, domains,force,renew, email)
file = self.getHostConf(site_name)
@ -1702,20 +1792,7 @@ location ^~ {from} {\n\
srcPath = siteInfo['path']
# 检测acme是否安装
acme_dir = mw.getAcmeDir()
if not os.path.exists(acme_dir):
try:
mw.execShell("curl -sS curl https://get.acme.sh | sh")
except:
pass
if not os.path.exists(acme_dir):
return mw.returnData(False, '尝试自动安装ACME失败,请通过以下命令尝试手动安装<p>安装命令: curl https://get.acme.sh | sh</p>')
# 避免频繁执行
checkAcmeRun = mw.execShell('ps -ef|grep acme.sh |grep -v grep')
if checkAcmeRun[0] != '':
return mw.returnData(False, '正在申请或更新SSL中...')
if force == 'true':
force_bool = True
@ -1813,7 +1890,7 @@ location ^~ {from} {\n\
top_domain = s[last_index-1]+'.'+s[last_index]
return top_domain
def createAcmeDns(self, site_name, domains, dnspai, force, renew):
def createAcmeDns(self, site_name, domains, dnspai, wildcard_domain, force, renew):
dnsapi_option = thisdb.getOptionByJson('dnsapi', default={})
if not dnspai in dnsapi_option:
return mw.returnData(False, dnspai+'未设置')
@ -1823,29 +1900,187 @@ location ^~ {from} {\n\
if dnsapi_data[k] == '':
return mw.returnData(False, k+'为空!')
cmd = self.getDnsapiExportVar(dnsapi_data)
acme_dir = mw.getAcmeDir()
for d in domains:
top_domain = self.getDomainRootName(d)
cmd += 'acme.sh --issue --dns '+str(dnspai)+' -d '+top_domain+' -d "*.'+top_domain+'" --force'
cmd = '''
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin:%s
export PATH
''' % (acme_dir,)
cmd += self.getDnsapiExportVar(dnsapi_data)
if wildcard_domain == 'true':
top_domain = self.getDomainRootName(d)
cmd += 'acme.sh --issue --dns '+str(dnspai)+' -d '+top_domain+' -d "*.'+top_domain+'"'
d = top_domain
else:
cmd += 'acme.sh --issue --dns '+str(dnspai)+' -d '+d
log_file = self.acmeLogFile()
cmd += ' >> ' + log_file
print(cmd)
result = mw.execShell(cmd)
print(result)
# acme源的ssl证书
src_path = mw.getAcmeDomainDir(d)
src_cert = src_path + '/fullchain.cer'
src_key = src_path + '/' + d + '.key'
src_cert.replace("\\*", "*")
msg = '签发失败,您尝试申请证书的失败次数已达上限!\
<p>1检查域名是否正确解析到本服务器,或解析还未完全生效</p>\
<p>2如果以上检查都确认没有问题请尝试更换DNS服务商</p>'
if not os.path.exists(src_cert):
data = {}
data['err'] = result
data['out'] = result[0]
data['msg'] = msg
data['result'] = {}
if result[1].find('new-authz error:') != -1:
data['result'] = json.loads(re.search("{.+}", result[1]).group())
if data['result']['status'] == 429:
data['msg'] = msg
data['status'] = False
return data
# acme源建立软链接(目标)
dst_path = self.sslDir + '/' + site_name
dst_cert = dst_path + "/fullchain.pem" # 生成证书路径
dst_key = dst_path + "/privkey.pem" # 密钥文件路径
if not os.path.exists(dst_path):
mw.execShell("mkdir -p " + dst_path)
mw.buildSoftLink(src_cert, dst_cert, True)
mw.buildSoftLink(src_key, dst_key, True)
mw.execShell('echo "acme" > "' + dst_path + '/README"')
# 写入配置文件
result = self.setSslConf(site_name)
if not result['status']:
return result
result['csr'] = mw.readFile(src_cert)
result['key'] = mw.readFile(src_key)
print(dnsapi_data)
print(domains)
print(cmd)
return mw.returnData(False, '测试中!')
mw.restartWeb()
return mw.returnData(True, '证书已更新!', result)
def createAcme(self, site_name, domains,force,renew, apply_type, dnspai, email):
def createAcme(self, site_name, domains,force,renew, apply_type, dnspai, email, wildcard_domain):
domains = json.loads(domains)
if len(domains) < 1:
return mw.returnData(False, '请选择域名')
if email.strip() != '':
thisdb.setOption('ssl_email', email)
# 检测acme是否安装
acme_dir = mw.getAcmeDir()
if not os.path.exists(acme_dir):
try:
mw.execShell("curl -sS curl https://get.acme.sh | sh")
except:
pass
if not os.path.exists(acme_dir):
return mw.returnData(False, '尝试自动安装ACME失败,请通过以下命令尝试手动安装<p>安装命令: curl https://get.acme.sh | sh</p>')
# 避免频繁执行
checkAcmeRun = mw.execShell('ps -ef|grep acme.sh |grep -v grep')
if checkAcmeRun[0] != '':
return mw.returnData(False, '正在申请或更新SSL中...')
if apply_type == 'file':
return self.createAcmeFile(site_name, domains,force,renew, apply_type, dnspai, email)
return self.createAcmeFile(site_name, domains, email,force,renew)
elif apply_type == 'dns':
return self.createAcmeDns(site_name, domains, dnspai,force, renew)
return mw.returnData(False, '异常请求')
return self.createAcmeDns(site_name, domains, dnspai, wildcard_domain,force, renew)
return mw.returnData(False, '异常请求')
def createLet(self, site_name, domains, force, renew, apply_type, dnspai, email, wildcard_domain):
siteName = request.form.get('siteName', '')
domains = request.form.get('domains', '')
force = request.form.get('force', '')
renew = request.form.get('renew', '')
email_args = request.form.get('email', '')
domains = json.loads(domains)
email = mw.M('users').getField('email')
if email_args.strip() != '':
mw.M('users').setField('email', email_args)
email = email_args
if not len(domains):
return mw.returnJson(False, '请选择域名')
host_conf_file = self.getHostConf(siteName)
if os.path.exists(host_conf_file):
siteConf = mw.readFile(host_conf_file)
if siteConf.find('301-END') != -1:
return mw.returnJson(False, '检测到您的站点做了301重定向设置,请先关闭重定向!')
# 检测存在反向代理
data_path = self.getProxyDataPath(siteName)
data_content = mw.readFile(data_path)
if data_content != False:
try:
data = json.loads(data_content)
except:
pass
for proxy in data:
proxy_dir = "{}/{}".format(self.proxyPath, siteName)
proxy_dir_file = proxy_dir + '/' + proxy['id'] + '.conf'
if os.path.exists(proxy_dir_file):
return mw.returnJson(False, '检测到您的站点做了反向代理设置,请先关闭反向代理!')
# fix binddir domain ssl apply question
mw.backFile(host_conf_file)
auth_to = self.getSitePath(siteName)
rep = r"\s*root\s*(.+);"
replace_root = "\n\troot " + auth_to + ";"
siteConf = re.sub(rep, replace_root, siteConf)
mw.writeFile(host_conf_file, siteConf)
mw.restartWeb()
to_args = {
'domains': domains,
'auth_type': 'http',
'auth_to': auth_to,
}
src_letpath = mw.getServerDir() + '/web_conf/letsencrypt/' + siteName
src_csrpath = src_letpath + "/fullchain.pem" # 生成证书路径
src_keypath = src_letpath + "/privkey.pem" # 密钥文件路径
dst_letpath = self.sslDir + '/' + siteName
dst_csrpath = dst_letpath + '/fullchain.pem'
dst_keypath = dst_letpath + '/privkey.pem'
if not os.path.exists(src_letpath):
import cert_api
data = cert_api.cert_api().applyCertApi(to_args)
mw.restoreFile(host_conf_file)
if not data['status']:
msg = data['msg']
if type(data['msg']) != str:
msg = data['msg'][0]
emsg = data['msg'][1]['challenges'][0]['error']
msg = msg + '<p><span>响应状态:</span>' + str(emsg['status']) + '</p><p><span>错误类型:</span>' + emsg[
'type'] + '</p><p><span>错误代码:</span>' + emsg['detail'] + '</p>'
return mw.returnJson(data['status'], msg, data['msg'])
mw.execShell('mkdir -p ' + dst_letpath)
mw.buildSoftLink(src_csrpath, dst_csrpath, True)
mw.buildSoftLink(src_keypath, dst_keypath, True)
mw.execShell('echo "lets" > "' + dst_letpath + '/README"')
# 写入配置文件
result = self.setSslConf(siteName)
if not result['status']:
return mw.getJson(result)
result['csr'] = mw.readFile(src_csrpath)
result['key'] = mw.readFile(src_keypath)
mw.restartWeb()
return mw.returnData(data['status'], data['msg'], result)
def getPhpVersion(self):

Loading…
Cancel
Save