Commit 161499b9 authored by jose's avatar jose

Refactored Let'sEncrypt one-click deployment module

parent 93cc3323
......@@ -19,6 +19,7 @@ from flask_session import Session
from werkzeug.contrib.cache import SimpleCache
from werkzeug.wrappers import Response
from flask_socketio import SocketIO,emit,send
dns_client = None
#设置BasicAuth
basic_auth_conf = 'config/basic_auth.json'
......@@ -178,9 +179,9 @@ def is_login(result):
if 'login' in session:
if session['login'] == True:
result = make_response(result)
request_token = public.md5(app.secret_key + str(time.time()))
request_token = public.GetRandomString(48)
session['request_token'] = request_token
result.set_cookie('request_token',request_token,httponly=True,max_age=86400*30)
result.set_cookie('request_token',request_token,max_age=86400*30)
return result
@app.route('/site',methods=method_all)
......@@ -403,7 +404,9 @@ def config(pdata = None):
workers_p = 'data/workers.pl'
if not os.path.exists(workers_p): public.writeFile(workers_p,'1')
data['workers'] = int(public.readFile(workers_p))
data['session_timeout'] = int(public.readFile(sess_out_path))
s_time_tmp = public.readFile(sess_out_path)
if not s_time_tmp: s_time_tmp = '0'
data['session_timeout'] = int(s_time_tmp)
if c_obj.get_ipv6_listen(None): data['ipv6'] = 'checked'
if c_obj.get_token(None)['open']: data['api'] = 'checked'
data['basic_auth'] = c_obj.get_basic_auth_stat(None)
......@@ -420,7 +423,7 @@ def ajax(pdata = None):
if comReturn: return comReturn
import ajax
ajaxObject = ajax.ajax()
defs = ('check_user_auth','to_not_beta','get_beta_logs','apple_beta','GetApacheStatus','GetCloudHtml','get_load_average','GetOpeLogs','GetFpmLogs','GetFpmSlowLogs','SetMemcachedCache','GetMemcachedStatus','GetRedisStatus','GetWarning','SetWarning','CheckLogin','GetSpeed','GetAd','phpSort','ToPunycode','GetBetaStatus','SetBeta','setPHPMyAdmin','delClose','KillProcess','GetPHPInfo','GetQiniuFileList','UninstallLib','InstallLib','SetQiniuAS','GetQiniuAS','GetLibList','GetProcessList','GetNetWorkList','GetNginxStatus','GetPHPStatus','GetTaskCount','GetSoftList','GetNetWorkIo','GetDiskIo','GetCpuIo','CheckInstalled','UpdatePanel','GetInstalled','GetPHPConfig','SetPHPConfig')
defs = ('set_phpmyadmin_ssl','get_phpmyadmin_ssl','check_user_auth','to_not_beta','get_beta_logs','apple_beta','GetApacheStatus','GetCloudHtml','get_load_average','GetOpeLogs','GetFpmLogs','GetFpmSlowLogs','SetMemcachedCache','GetMemcachedStatus','GetRedisStatus','GetWarning','SetWarning','CheckLogin','GetSpeed','GetAd','phpSort','ToPunycode','GetBetaStatus','SetBeta','setPHPMyAdmin','delClose','KillProcess','GetPHPInfo','GetQiniuFileList','UninstallLib','InstallLib','SetQiniuAS','GetQiniuAS','GetLibList','GetProcessList','GetNetWorkList','GetNginxStatus','GetPHPStatus','GetTaskCount','GetSoftList','GetNetWorkIo','GetDiskIo','GetCpuIo','CheckInstalled','UpdatePanel','GetInstalled','GetPHPConfig','SetPHPConfig')
return publicObject(ajaxObject,defs,None,pdata);
@app.route('/system',methods=method_all)
......@@ -481,7 +484,7 @@ def ssl(pdata = None):
if comReturn: return comReturn
import panelSSL
toObject = panelSSL.panelSSL()
defs = ('RemoveCert','SetCertToSite','GetCertList','SaveCert','GetCert','GetCertName','DelToken','GetToken','GetUserInfo','GetOrderList','GetDVSSL','Completed','SyncOrder','GetSSLInfo','downloadCRT','GetSSLProduct','Renew_SSL','Get_Renew_SSL')
defs = ('RemoveCert','renew_lets_ssl','SetCertToSite','GetCertList','SaveCert','GetCert','GetCertName','DelToken','GetToken','GetUserInfo','GetOrderList','GetDVSSL','Completed','SyncOrder','GetSSLInfo','downloadCRT','GetSSLProduct','Renew_SSL','Get_Renew_SSL')
result = publicObject(toObject,defs,None,pdata);
return result;
......@@ -873,14 +876,15 @@ except:
@socketio.on('webssh')
def webssh(msg):
if not check_login():
if not check_login(msg['x_http_token']):
emit('server_response',{'data':public.getMsg('INIT_WEBSSH_LOGOUT')})
return None
global shell,ssh
ssh_success = True
if type(msg) == dict:
if 'ssh_user' in msg:
connect_ssh(msg['ssh_user'].strip(),msg['ssh_passwd'].strip())
if type(msg['data']) == dict:
if 'ssh_user' in msg['data']:
connect_ssh(msg['data']['ssh_user'].strip(),msg['data']['ssh_passwd'].strip())
if not shell: ssh_success = connect_ssh()
if not shell:
emit('server_response',{'data':public.getMsg('INIT_WEBSSH_CONN_ERR')})
......@@ -889,13 +893,10 @@ def webssh(msg):
if not ssh_success:
emit('server_response',{'data':public.getMsg('INIT_WEBSSH_CONN_ERR')})
return;
shell.send(msg)
try:
time.sleep(0.005)
recv = shell.recv(4096)
emit('server_response',{'data':recv.decode("utf-8")})
except Exception as ex:
pass
shell.send(msg['data'])
time.sleep(0.005)
recv = shell.recv(4096)
emit('server_response',{'data':recv.decode("utf-8")})
def connect_ssh(user=None,passwd=None):
global shell,ssh
......@@ -964,35 +965,25 @@ def connected_msg(msg):
if not shell: connect_ssh()
if shell:
try:
#shell.send(msg)
recv = shell.recv(8192)
emit('server_response',{'data':recv.decode("utf-8")})
except:
pass
@socketio.on('panel')
def websocket_test(data):
pdata = data
if not check_login():
emit(pdata.s_response,{'data':public.returnMsg(-1,public.getMsg('INIT_WEBSSH_LOGOUT'))})
return None
mods = ['site','ftp','database','ajax','system','crontab','files','config','panel_data','plugin','ssl','auth','firewall','panel_wxapp']
if not pdata['s_module'] in mods:
result = public.returnMsg(False,"INIT_WEBSOCKET_ERR")
else:
result = eval("%s(pdata)" % pdata['s_module'])
if not hasattr(pdata,'s_response'): pdata.s_response = 'response'
emit(pdata.s_response,{'data':result})
def check_csrf():
request_token = request.cookies.get('request_token')
if session['request_token'] != request_token: return False
http_token = request.headers.get('x-http-token')
if not http_token: return False
if http_token != session['request_token_head']: return False
cookie_token = request.headers.get('x-cookie-token')
if cookie_token != session['request_token']: return False
return True
def publicObject(toObject,defs,action=None,get = None):
if 'request_token' in session and 'login' in session:
request_token = request.cookies.get('request_token')
if session['request_token'] != request_token:
if session['login'] != False:
session['login'] = False;
cache.set('dologin',True)
return redirect('/login')
if not check_csrf(): return public.ReturnJson(False,'Csrf-Token error.'),json_header
if not get: get = get_input()
if action: get.action = action
......@@ -1015,10 +1006,12 @@ def publicObject(toObject,defs,action=None,get = None):
return public.ReturnJson(False,'ARGS_ERR'),json_header
def check_login():
def check_login(http_token=None):
if cache.get('dologin'): return False
if 'login' in session:
loginStatus = session['login']
if loginStatus and http_token:
if session['request_token_head'] != http_token: return False
return loginStatus
return False
......
This diff is collapsed.
......@@ -27,7 +27,7 @@ class panelSetup:
if ua:
ua = ua.lower();
if ua.find('spider') != -1 or ua.find('bot') != -1: return redirect('https://www.baidu.com');
g.version = '6.1.1'
g.version = '6.1.2'
g.title = public.GetConfigValue('title')
g.uri = request.path
session['version'] = g.version;
......@@ -142,6 +142,13 @@ class panelAdmin(panelSetup):
return redirect('/login')
public.writeFile(sess_input_path,str(int(time.time())))
except:pass
filename = '/www/server/panel/data/login_token.pl'
if os.path.exists(filename):
token = public.readFile(filename).strip()
if 'login_token' in session:
if session['login_token'] != token:
return redirect('/login?dologin=True')
except:
return redirect('/login')
......
......@@ -15,11 +15,18 @@ class config:
def getPanelState(self,get):
return os.path.exists('/www/server/panel/data/close.pl');
def reload_session(self):
userInfo = public.M('users').where("id=?",(1,)).field('username,password').find()
token = public.Md5(userInfo['username'] + '/' + userInfo['password'])
public.writeFile('/www/server/panel/data/login_token.pl',token)
session['login_token'] = token
def setPassword(self,get):
if get.password1 != get.password2: return public.returnMsg(False,'USER_PASSWORD_CHECK')
if len(get.password1) < 5: return public.returnMsg(False,'USER_PASSWORD_LEN')
public.M('users').where("username=?",(session['username'],)).setField('password',public.md5(get.password1.strip()))
public.WriteLog('TYPE_PANEL','USER_PASSWORD_SUCCESS',(session['username'],))
self.reload_session()
return public.returnMsg(True,'USER_PASSWORD_SUCCESS')
def setUsername(self,get):
......@@ -28,6 +35,7 @@ class config:
public.M('users').where("username=?",(session['username'],)).setField('username',get.username1.strip())
public.WriteLog('TYPE_PANEL','USER_USERNAME_SUCCESS',(session['username'],get.username2))
session['username'] = get.username1
self.reload_session()
return public.returnMsg(True,'USER_USERNAME_SUCCESS')
def setPanel(self,get):
......@@ -36,8 +44,10 @@ class config:
sess_out_path = 'data/session_timeout.pl'
if 'session_timeout' in get:
session_timeout = int(get.session_timeout)
if int(public.readFile(sess_out_path)) != session_timeout:
if session_timeout < 300: return public.returnMsg(False,public.GetMsg("NOT_LESS_THAN_TIMEOUT"))
s_time_tmp = public.readFile(sess_out_path)
if not s_time_tmp: s_time_tmp = '0'
if int(s_time_tmp) != session_timeout:
if session_timeout < 300: return public.returnMsg(False,'NOT_LESS_THAN_TIMEOUT')
public.writeFile(sess_out_path,str(session_timeout))
isReWeb = True
......@@ -411,9 +421,6 @@ class config:
os.system('rm -f ' + sslConf);
return public.returnMsg(True,'PANEL_SSL_CLOSE');
else:
os.system('pip insatll cffi==1.10');
os.system('pip install cryptography==2.1');
os.system('pip install pyOpenSSL==16.2');
try:
if not self.CreateSSL(): return public.returnMsg(False,'PANEL_SSL_ERR');
public.writeFile(sslConf,'True')
......
This diff is collapsed.
This diff is collapsed.
......@@ -357,6 +357,7 @@ class panelSSL:
data['dns'] = result[0].replace('DNS:','').replace(' ','').strip().split(',');
return data;
except:
print(public.get_error_info())
return None;
#转换时间
......@@ -393,90 +394,32 @@ class panelSSL:
if type(result) != str: result = result.decode('utf-8')
return json.loads(result);
# 手动一键续签
def Renew_SSL(self, get):
if not os.path.isfile("/www/server/panel/vhost/crontab.json"):
return {"status": False, "msg": public.GetMsg("NOT_RENEW_CERT")}
cmd_list = json.loads(public.ReadFile("/www/server/panel/vhost/crontab.json"))
import panelTask
task = panelTask.bt_task()
Renew = True
for xt in task.get_task_list():
if xt['status'] != 1: Renew = False
if not Renew:
return {"status": False, "msg": public.GetMsg("EXIST_RENEW_TASK")}
for j in cmd_list:
siteName = j['siteName']
home_path = os.path.join("/www/server/panel/vhost/cert/", siteName)
public.ExecShell("mkdir -p {}".format(home_path))
public.ExecShell('''cd {} && rm -rf check_authorization_status_response Confirmation_verification domain_txt_dns_value.json apply_for_cert_issuance_response timeout_info'''.format(home_path))
cmd = j['cmd']
for x in task.get_task_list():
if x['name'] == siteName:
get.id = x['id']
task.remove_task(get) # 删除旧的任务
task.create_task(siteName, 0, cmd)
return {"status": True, "msg": public.GetMsg("ADD_RENEW_TO_TASK")}
# 手动一键续签
def renew_lets_ssl(self, get):
if not os.path.exists('vhost/cert/crontab.json'):
return public.returnMsg(False,'There are currently no certificates to renew!')
# 获取一键续订结果
def Get_Renew_SSL(self, get):
if not os.path.isfile("/www/server/panel/vhost/crontab.json"):
return {"status": False, "msg": public.GetMsg("GET_FAIL_NOT_RESULT"), "data": []}
cmd_list = json.loads(public.ReadFile("/www/server/panel/vhost/crontab.json"))
import panelTask
CertList = self.GetCertList(get)
data = []
for j in cmd_list:
siteName = j['siteName']
cmd = j['cmd']
home_path = os.path.join("/www/server/panel/vhost/cert/", siteName)
home_csr = os.path.join(home_path, "fullchain.pem")
home_key = os.path.join(home_path, "privkey.pem")
old_list = json.loads(public.ReadFile("vhost/cert/crontab.json"))
cron_list = old_list
if hasattr(get, 'siteName'):
if not get.siteName in old_list:
return public.returnMsg(False,'There is no certificate that can be renewed on the current website..')
cron_list = {}
cron_list[get.siteName] = old_list[get.siteName]
task = panelTask.bt_task()
for i in task.get_task_list():
if i['name'] == siteName:
siteName_task = {'status': i['status']}
siteName_task['subject'] = siteName
siteName_task['dns'] = [siteName, ]
for item in CertList:
if siteName == item['subject']:
siteName_task['dns'] = item['dns']
siteName_task['notAfter'] = item['notAfter']
siteName_task['issuer'] = item['issuer']
timeArray = time.localtime(i['addtime'])
siteName_task['addtime'] = time.strftime("%Y-%m-%d %H:%M:%S", timeArray)
if i['endtime']:
timeArray = time.localtime(i['endtime'])
siteName_task['endtime'] = time.strftime("%Y-%m-%d %H:%M:%S", timeArray)
else:
siteName_task['endtime'] = i['endtime']
if i['status'] == -1:
siteName_task['msg'] = public.GetMsg("RENEW_NOW")
if i['status'] == 0:
siteName_task['msg'] = public.GetMsg("WAIT_RENEW")
if i['status'] == 1:
get.keyPath =home_key
get.certPath = home_csr
self.SaveCert(get);
siteName_task['msg'] = public.GetMsg("RENEW_SUCCESS")
siteName_task['status'] = True
if not os.path.isfile(home_key) and not os.path.isfile(home_csr):
siteName_task['msg'] = public.GetMsg("RENEW_FAIL")
siteName_task['status'] = False
if os.path.isfile(os.path.join(home_path, "check_authorization_status_response")):
siteName_task['msg'] = public.GetMsg("RENEW_FAIL1")
siteName_task['status'] = False
if os.path.isfile(os.path.join(home_path, "apply_for_cert_issuance_response")):
siteName_task['msg'] = public.GetMsg("RENEW_FAIL2")
siteName_task['status'] = False
import panelLets
lets = panelLets.panelLets()
data.append(siteName_task)
break
if data:
return {"status": True, "msg": public.GetMsg("SSL_GET_SUCCESS"), "data": data}
else:
return {"status": False, "msg": public.GetMsg("GET_FAIL_NOT_RESULT"), "data": []}
\ No newline at end of file
result = {}
result['status'] = True
result['sucess_list'] = []
result['err_list'] = []
for siteName in cron_list:
data = cron_list[siteName]
ret = lets.renew_lest_cert(data)
if ret['status']:
result['sucess_list'].append(siteName)
else:
result['err_list'].append({"siteName":siteName,"msg":ret['msg']})
return result;
This diff is collapsed.
......@@ -708,6 +708,12 @@ def inArray(arrays,searchStr):
return False
#格式化指定时间戳
def format_date(format="%Y-%m-%d %H:%M:%S",times = None):
if not times: times = int(time.time())
time_local = time.localtime(times)
return time.strftime(format, time_local)
#检查Web服务器配置文件是否有错误
def checkWebConfig():
......@@ -1252,4 +1258,4 @@ def set_own(filename,user,group=None):
user = user_info.pw_uid
group = user_info.pw_gid
os.chown(filename,user,group)
return True
\ No newline at end of file
return True
from .client import Client # noqa: F401
from .dns_providers import BaseDns # noqa: F401
from .dns_providers import AuroraDns # noqa: F401
from .dns_providers import CloudFlareDns # noqa: F401
from .dns_providers import AcmeDnsDns # noqa: F401
from .dns_providers import AliyunDns # noqa:F401
from .dns_providers import HurricaneDns # noqa:F401
from .dns_providers import RackspaceDns # noqa:F401
from .dns_providers import DNSPodDns
from .dns_providers import DuckDNSDns
__title__ = "sewer"
__description__ = "Sewer is a programmatic Lets Encrypt(ACME) client"
__url__ = "https://github.com/komuw/sewer"
__version__ = "0.7.2"
__author__ = "komuW"
__author_email__ = "komuw05@gmail.com"
__license__ = "MIT"
This diff is collapsed.
This diff is collapsed.
ACME_DIRECTORY_URL_STAGING = "https://acme-staging-v02.api.letsencrypt.org/directory"
ACME_DIRECTORY_URL_PRODUCTION = "https://acme-v02.api.letsencrypt.org/directory"
from .common import BaseDns # noqa: F401
from .auroradns import AuroraDns # noqa: F401
from .cloudflare import CloudFlareDns # noqa: F401
from .acmedns import AcmeDnsDns # noqa: F401
from .aliyundns import AliyunDns # noqa: F401
from .hurricane import HurricaneDns # noqa: F401
from .rackspace import RackspaceDns # noqa: F401
from .dnspod import DNSPodDns
from .duckdns import DuckDNSDns
try:
import urllib.parse as urlparse
except:
import urlparse
try:
acmedns_dependencies = True
from dns.resolver import Resolver
except ImportError:
acmedns_dependencies = False
import requests
from . import common
class AcmeDnsDns(common.BaseDns):
"""
"""
dns_provider_name = "acmedns"
def __init__(self, ACME_DNS_API_USER, ACME_DNS_API_KEY, ACME_DNS_API_BASE_URL):
if not acmedns_dependencies:
raise ImportError(
"""You need to install AcmeDnsDns dependencies. run; pip3 install sewer[acmedns]"""
)
self.ACME_DNS_API_USER = ACME_DNS_API_USER
self.ACME_DNS_API_KEY = ACME_DNS_API_KEY
self.HTTP_TIMEOUT = 65 # seconds
if ACME_DNS_API_BASE_URL[-1] != "/":
self.ACME_DNS_API_BASE_URL = ACME_DNS_API_BASE_URL + "/"
else:
self.ACME_DNS_API_BASE_URL = ACME_DNS_API_BASE_URL
super(AcmeDnsDns, self).__init__()
def create_dns_record(self, domain_name, domain_dns_value):
self.logger.info("create_dns_record")
# if we have been given a wildcard name, strip wildcard
domain_name = domain_name.lstrip("*.")
resolver = Resolver(configure=False)
resolver.nameservers = ["8.8.8.8"]
answer = resolver.query("_acme-challenge.{0}.".format(domain_name), "TXT")
subdomain, _ = str(answer.canonical_name).split(".", 1)
url = urlparse.urljoin(self.ACME_DNS_API_BASE_URL, "update")
headers = {"X-Api-User": self.ACME_DNS_API_USER, "X-Api-Key": self.ACME_DNS_API_KEY}
body = {"subdomain": subdomain, "txt": domain_dns_value}
update_acmedns_dns_record_response = requests.post(
url, headers=headers, json=body, timeout=self.HTTP_TIMEOUT
)
self.logger.debug(
"update_acmedns_dns_record_response. status_code={0}. response={1}".format(
update_acmedns_dns_record_response.status_code,
self.log_response(update_acmedns_dns_record_response),
)
)
if update_acmedns_dns_record_response.status_code != 200:
# raise error so that we do not continue to make calls to ACME
# server
raise ValueError(
"Error creating acme-dns dns record: status_code={status_code} response={response}".format(
status_code=update_acmedns_dns_record_response.status_code,
response=self.log_response(update_acmedns_dns_record_response),
)
)
self.logger.info("create_dns_record_end")
def delete_dns_record(self, domain_name, domain_dns_value):
self.logger.info("delete_dns_record")
# acme-dns doesn't support this
self.logger.info("delete_dns_record_success")
import json
try:
aliyun_dependencies = True
from aliyunsdkcore import client
from aliyunsdkalidns.request.v20150109 import DescribeDomainRecordsRequest
from aliyunsdkalidns.request.v20150109 import AddDomainRecordRequest
from aliyunsdkalidns.request.v20150109 import DeleteDomainRecordRequest
except ImportError:
aliyun_dependencies = False
from . import common
class _ResponseForAliyun(object):
"""
wrapper aliyun resp to the format sewer wanted.
"""
def __init__(self, status_code=200, content=None, headers=None):
self.status_code = status_code
self.headers = headers or {}
self.content = content or {}
self.content = json.dumps(content)
super(_ResponseForAliyun, self).__init__()
def json(self):
return json.loads(self.content)
class AliyunDns(common.BaseDns):
def __init__(self, key, secret, endpoint="cn-beijing", debug=False):
"""
aliyun dns client
:param str key: access key
:param str secret: access sceret
:param str endpoint: endpoint
:param bool debug: if debug?
"""
super(AliyunDns, self).__init__()
if not aliyun_dependencies:
raise ImportError(
"""You need to install aliyunDns dependencies. run; pip3 install sewer[aliyun]"""
)
self._key = key
self._secret = secret
self._endpoint = endpoint
self._debug = debug
self.clt = client.AcsClient(self._key, self._secret, self._endpoint, debug=self._debug)
def _send_reqeust(self, request):
"""
send request to aliyun
"""
request.set_accept_format("json")
try:
status, headers, result = self.clt.implementation_of_do_action(request)
result = json.loads(result)
if "Message" in result or "Code" in result:
result["Success"] = False
self.logger.warning("aliyundns resp error: %s", result)
except Exception as exc:
self.logger.warning("aliyundns failed to send request: %s, %s", str(exc), request)
status, headers, result = 502, {}, '{"Success": false}'
result = json.loads(result)
if self._debug:
self.logger.info("aliyundns request name: %s", request.__class__.__name__)
self.logger.info("aliyundns request query: %s", request.get_query_params())
return _ResponseForAliyun(status, result, headers)
def query_recored_items(self, host, zone=None, tipe=None, page=1, psize=200):
"""
query recored items.
:param str host: like example.com
:param str zone: like menduo.example.com
:param str tipe: TXT, CNAME, IP or other
:param int page:
:param int psize:
:return dict: res = {
'DomainRecords':
{'Record': [
{
'DomainName': 'menduo.net',
'Line': 'default',
'Locked': False,
'RR': 'zb',
'RecordId': '3989515483698964',
'Status': 'ENABLE',
'TTL': 600,
'Type': 'A',
'Value': '127.0.0.1',
'Weight': 1
},
{
'DomainName': 'menduo.net',
'Line': 'default',
'Locked': False,
'RR': 'a.sub',
'RecordId': '3989515480778964',
'Status': 'ENABLE',
'TTL': 600,
'Type': 'CNAME',
'Value': 'h.p.menduo.net',
'Weight': 1
}
]
},
'PageNumber': 1,
'PageSize': 20,
'RequestId': 'FC4D02CD-EDCC-4EE8-942F-1497CCC3B10E',
'TotalCount': 95
}
"""
request = DescribeDomainRecordsRequest.DescribeDomainRecordsRequest()
request.get_action_name()
request.set_DomainName(host)
request.set_PageNumber(page)
request.set_PageSize(psize)
if zone:
request.set_RRKeyWord(zone)
if tipe:
request.set_TypeKeyWord(tipe)
resp = self._send_reqeust(request)
body = resp.json()
return body
def query_recored_id(self, root, zone, tipe="TXT"):
"""
find recored
:param str root: root host, like example.com
:param str zone: sub zone, like menduo.example.com
:param str tipe: record tipe, TXT, CNAME, IP. we use TXT
:return str:
"""
record_id = None
recoreds = self.query_recored_items(root, zone, tipe=tipe)
recored_list = recoreds.get("DomainRecords", {}).get("Record", [])
recored_item_list = [i for i in recored_list if i["RR"] == zone]
if len(recored_item_list):
record_id = recored_item_list[0]["RecordId"]
return record_id
@staticmethod
def extract_zone(domain_name):
"""
extract domain to root, sub, acme_txt
:param str domain_name: the value sewer client passed in, like *.menduo.example.com
:return tuple: root, zone, acme_txt
"""
# if we have been given a wildcard name, strip wildcard
domain_name = domain_name.lstrip("*.")
if domain_name.count(".") > 1:
zone, middle, last = str(domain_name).rsplit(".", 2)
root = ".".join([middle, last])
acme_txt = "_acme-challenge.%s" % zone
else:
zone = ""
root = domain_name
acme_txt = "_acme-challenge"
return root, zone, acme_txt
def create_dns_record(self, domain_name, domain_dns_value):
"""
create a dns record
:param str domain_name: the value sewer client passed in, like *.menduo.example.com
:param str domain_dns_value: the value sewer client passed in.
:return _ResponseForAliyun:
"""
self.logger.info("create_dns_record start: %s", (domain_name, domain_dns_value))
root, _, acme_txt = self.extract_zone(domain_name)
request = AddDomainRecordRequest.AddDomainRecordRequest()
request.set_DomainName(root)
request.set_TTL(600)
request.set_RR(acme_txt)
request.set_Type("TXT")
request.set_Value(domain_dns_value)
resp = self._send_reqeust(request)
self.logger.info("create_dns_record end: %s", (domain_name, domain_dns_value, resp.json()))
return resp
def delete_dns_record(self, domain_name, domain_dns_value):
"""
delete a txt record we created just now.
:param str domain_name: the value sewer client passed in, like *.menduo.example.com
:param str domain_dns_value: the value sewer client passed in. we do not use this.
:return _ResponseForAliyun:
:return:
"""
self.logger.info("delete_dns_record start: %s", (domain_name, domain_dns_value))
root, _, acme_txt = self.extract_zone(domain_name)
record_id = self.query_recored_id(root, acme_txt)
if not record_id:
msg = "failed to find record_id of domain: %s, value: %s", domain_name, domain_dns_value
self.logger.warning(msg)
return
self.logger.info("start to delete dns record, id: %s", record_id)
request = DeleteDomainRecordRequest.DeleteDomainRecordRequest()
request.set_RecordId(record_id)
resp = self._send_reqeust(request)
self.logger.info("delete_dns_record end: %s", (domain_name, domain_dns_value, resp.json()))
return resp
# DNS Provider for AuroRa DNS from the dutch hosting provider pcextreme
# https://www.pcextreme.nl/aurora/dns
# Aurora uses libcloud from apache
# https://libcloud.apache.org/
try:
aurora_dependencies = True
from libcloud.dns.providers import get_driver
from libcloud.dns.types import Provider, RecordType
import tldextract
except ImportError:
aurora_dependencies = False
from . import common
class AuroraDns(common.BaseDns):
"""
Todo: re-organize this class so that we make it easier to mock things out to
facilitate better tests.
"""
dns_provider_name = "aurora"
def __init__(self, AURORA_API_KEY, AURORA_SECRET_KEY):
if not aurora_dependencies:
raise ImportError(
"""You need to install AuroraDns dependencies. run; pip3 install sewer[aurora]"""
)
self.AURORA_API_KEY = AURORA_API_KEY
self.AURORA_SECRET_KEY = AURORA_SECRET_KEY
super(AuroraDns, self).__init__()
def create_dns_record(self, domain_name, domain_dns_value):
self.logger.info("create_dns_record")
# if we have been given a wildcard name, strip wildcard
domain_name = domain_name.lstrip("*.")
extractedDomain = tldextract.extract(domain_name)
domainSuffix = extractedDomain.domain + "." + extractedDomain.suffix
if extractedDomain.subdomain is "":
subDomain = "_acme-challenge"
else:
subDomain = "_acme-challenge." + extractedDomain.subdomain
cls = get_driver(Provider.AURORADNS)
driver = cls(key=self.AURORA_API_KEY, secret=self.AURORA_SECRET_KEY)
zone = driver.get_zone(domainSuffix)
zone.create_record(name=subDomain, type=RecordType.TXT, data=domain_dns_value)
self.logger.info("create_dns_record_success")
return
def delete_dns_record(self, domain_name, domain_dns_value):
self.logger.info("delete_dns_record")
extractedDomain = tldextract.extract(domain_name)
domainSuffix = extractedDomain.domain + "." + extractedDomain.suffix
if extractedDomain.subdomain is "":
subDomain = "_acme-challenge"
else:
subDomain = "_acme-challenge." + extractedDomain.subdomain
cls = get_driver(Provider.AURORADNS)
driver = cls(key=self.AURORA_API_KEY, secret=self.AURORA_SECRET_KEY)
zone = driver.get_zone(domainSuffix)
records = driver.list_records(zone)
for x in records:
if x.name == subDomain and x.type == "TXT":
record_id = x.id
self.logger.info(
"Found record "
+ subDomain
+ "."
+ domainSuffix
+ " with id : "
+ record_id
+ "."
)
record = driver.get_record(zone_id=zone.id, record_id=record_id)
driver.delete_record(record)
self.logger.info(
"Deleted record "
+ subDomain
+ "."
+ domainSuffix
+ " with id : "
+ record_id
+ "."
)
else:
self.logger.info(
"Record " + subDomain + "." + domainSuffix + " not found. No record to delete."
)
self.logger.info("delete_dns_record_success")
return
try:
import urllib.parse as urlparse
except:
import urlparse
import requests
from . import common
class CloudFlareDns(common.BaseDns):
"""
"""
dns_provider_name = "cloudflare"
def __init__(
self,
CLOUDFLARE_EMAIL,
CLOUDFLARE_API_KEY,
CLOUDFLARE_API_BASE_URL="https://api.cloudflare.com/client/v4/",
):
self.CLOUDFLARE_DNS_ZONE_ID = None
self.CLOUDFLARE_EMAIL = CLOUDFLARE_EMAIL
self.CLOUDFLARE_API_KEY = CLOUDFLARE_API_KEY
self.CLOUDFLARE_API_BASE_URL = CLOUDFLARE_API_BASE_URL
self.HTTP_TIMEOUT = 65 # seconds
if CLOUDFLARE_API_BASE_URL[-1] != "/":
self.CLOUDFLARE_API_BASE_URL = CLOUDFLARE_API_BASE_URL + "/"
else:
self.CLOUDFLARE_API_BASE_URL = CLOUDFLARE_API_BASE_URL
super(CloudFlareDns, self).__init__()
def find_dns_zone(self, domain_name):
self.logger.debug("find_dns_zone")
url = urlparse.urljoin(self.CLOUDFLARE_API_BASE_URL, "zones?status=active")
headers = {"X-Auth-Email": self.CLOUDFLARE_EMAIL, "X-Auth-Key": self.CLOUDFLARE_API_KEY}
find_dns_zone_response = requests.get(url, headers=headers, timeout=self.HTTP_TIMEOUT)
self.logger.debug(
"find_dns_zone_response. status_code={0}".format(find_dns_zone_response.status_code)
)
if find_dns_zone_response.status_code != 200:
raise ValueError(
"Error creating cloudflare dns record: status_code={status_code} response={response}".format(
status_code=find_dns_zone_response.status_code,
response=self.log_response(find_dns_zone_response),
)
)
result = find_dns_zone_response.json()["result"]
for i in result:
if i["name"] in domain_name:
setattr(self, "CLOUDFLARE_DNS_ZONE_ID", i["id"])
if isinstance(self.CLOUDFLARE_DNS_ZONE_ID, type(None)):
raise ValueError(
"Error unable to get DNS zone for domain_name={domain_name}: status_code={status_code} response={response}".format(
domain_name=domain_name,
status_code=find_dns_zone_response.status_code,
response=self.log_response(find_dns_zone_response),
)
)
self.logger.debug("find_dns_zone_success")
def create_dns_record(self, domain_name, domain_dns_value):
self.logger.info("create_dns_record")
# if we have been given a wildcard name, strip wildcard
domain_name = domain_name.lstrip("*.")
self.find_dns_zone(domain_name)
url = urllib.parse.urljoin(
self.CLOUDFLARE_API_BASE_URL,
"zones/{0}/dns_records".format(self.CLOUDFLARE_DNS_ZONE_ID),
)
headers = {"X-Auth-Email": self.CLOUDFLARE_EMAIL, "X-Auth-Key": self.CLOUDFLARE_API_KEY}
body = {
"type": "TXT",
"name": "_acme-challenge" + "." + domain_name + ".",
"content": "{0}".format(domain_dns_value),
}
create_cloudflare_dns_record_response = requests.post(
url, headers=headers, json=body, timeout=self.HTTP_TIMEOUT
)
self.logger.debug(
"create_cloudflare_dns_record_response. status_code={0}. response={1}".format(
create_cloudflare_dns_record_response.status_code,
self.log_response(create_cloudflare_dns_record_response),
)
)
if create_cloudflare_dns_record_response.status_code != 200:
# raise error so that we do not continue to make calls to ACME
# server
raise ValueError(
"Error creating cloudflare dns record: status_code={status_code} response={response}".format(
status_code=create_cloudflare_dns_record_response.status_code,
response=self.log_response(create_cloudflare_dns_record_response),
)
)
self.logger.info("create_dns_record_end")
def delete_dns_record(self, domain_name, domain_dns_value):
self.logger.info("delete_dns_record")
class MockResponse(object):
def __init__(self, status_code=200, content="mock-response"):
self.status_code = status_code
self.content = content
super(MockResponse, self).__init__()
def json(self):
return {}
delete_dns_record_response = MockResponse()
headers = {"X-Auth-Email": self.CLOUDFLARE_EMAIL, "X-Auth-Key": self.CLOUDFLARE_API_KEY}
dns_name = "_acme-challenge" + "." + domain_name
list_dns_payload = {"type": "TXT", "name": dns_name}
list_dns_url = urllib.parse.urljoin(
self.CLOUDFLARE_API_BASE_URL,
"zones/{0}/dns_records".format(self.CLOUDFLARE_DNS_ZONE_ID),
)
list_dns_response = requests.get(
list_dns_url, params=list_dns_payload, headers=headers, timeout=self.HTTP_TIMEOUT
)
for i in range(0, len(list_dns_response.json()["result"])):
dns_record_id = list_dns_response.json()["result"][i]["id"]
url = urllib.parse.urljoin(
self.CLOUDFLARE_API_BASE_URL,
"zones/{0}/dns_records/{1}".format(self.CLOUDFLARE_DNS_ZONE_ID, dns_record_id),
)
headers = {"X-Auth-Email": self.CLOUDFLARE_EMAIL, "X-Auth-Key": self.CLOUDFLARE_API_KEY}
delete_dns_record_response = requests.delete(
url, headers=headers, timeout=self.HTTP_TIMEOUT
)
self.logger.debug(
"delete_dns_record_response. status_code={0}. response={1}".format(
delete_dns_record_response.status_code,
self.log_response(delete_dns_record_response),
)
)
if delete_dns_record_response.status_code != 200:
# extended logging for debugging
# we do not need to raise exception
self.logger.error(
"delete_dns_record_response. status_code={0}. response={1}".format(
delete_dns_record_response.status_code,
self.log_response(delete_dns_record_response),
)
)
self.logger.info("delete_dns_record_success")
import logging
class BaseDns(object):
"""
"""
def __init__(self, LOG_LEVEL="INFO"):
self.LOG_LEVEL = LOG_LEVEL
self.dns_provider_name = self.__class__.__name__
self.logger = logging.getLogger("sewer")
handler = logging.StreamHandler()
formatter = logging.Formatter("%(message)s")
handler.setFormatter(formatter)
if not self.logger.handlers:
self.logger.addHandler(handler)
self.logger.setLevel(self.LOG_LEVEL)
def log_response(self, response):
"""
renders a python-requests response as json or as a string
"""
try:
log_body = response.json()
except ValueError:
log_body = response.content
return log_body
def create_dns_record(self, domain_name, domain_dns_value):
"""
Method that creates/adds a dns TXT record for a domain/subdomain name on
a chosen DNS provider.
:param domain_name: :string: The domain/subdomain name whose dns record ought to be
created/added on a chosen DNS provider.
:param domain_dns_value: :string: The value/content of the TXT record that will be
created/added for the given domain/subdomain
This method should return None
Basic Usage:
If the value of the `domain_name` variable is example.com and the value of
`domain_dns_value` is HAJA_4MkowIFByHhFaP8u035skaM91lTKplKld
Then, your implementation of this method ought to create a DNS TXT record
whose name is '_acme-challenge' + '.' + domain_name + '.' (ie: _acme-challenge.example.com. )
and whose value/content is HAJA_4MkowIFByHhFaP8u035skaM91lTKplKld
Using a dns client like dig(https://linux.die.net/man/1/dig) to do a dns lookup should result
in something like:
dig TXT _acme-challenge.example.com
...
;; ANSWER SECTION:
_acme-challenge.example.com. 120 IN TXT "HAJA_4MkowIFByHhFaP8u035skaM91lTKplKld"
_acme-challenge.singularity.brandur.org. 120 IN TXT "9C0DqKC_4MkowIFByHhFaP8u0Zv4z7Wz2IHM91lTKec"
Optionally, you may also use an online dns client like: https://toolbox.googleapps.com/apps/dig/#TXT/
Please consult your dns provider on how/format of their DNS TXT records.
You may also want to consult the cloudflare DNS implementation that is found in this repository.
"""
self.logger.info("create_dns_record")
raise NotImplementedError("create_dns_record method must be implemented.")
def delete_dns_record(self, domain_name, domain_dns_value):
"""
Method that deletes/removes a dns TXT record for a domain/subdomain name on
a chosen DNS provider.
:param domain_name: :string: The domain/subdomain name whose dns record ought to be
deleted/removed on a chosen DNS provider.
:param domain_dns_value: :string: The value/content of the TXT record that will be
deleted/removed for the given domain/subdomain
This method should return None
"""
self.logger.info("delete_dns_record")
raise NotImplementedError("delete_dns_record method must be implemented.")
try:
import urllib.parse as urlparse
except:
import urlparse
import requests
from . import common
class DNSPodDns(common.BaseDns):
"""
"""
dns_provider_name = "dnspod"
def __init__(self, DNSPOD_ID, DNSPOD_API_KEY, DNSPOD_API_BASE_URL="https://dnsapi.cn/"):
self.DNSPOD_ID = DNSPOD_ID
self.DNSPOD_API_KEY = DNSPOD_API_KEY
self.DNSPOD_API_BASE_URL = DNSPOD_API_BASE_URL
self.HTTP_TIMEOUT = 65 # seconds
self.DNSPOD_LOGIN = "{0},{1}".format(self.DNSPOD_ID, self.DNSPOD_API_KEY)
if DNSPOD_API_BASE_URL[-1] != "/":
self.DNSPOD_API_BASE_URL = DNSPOD_API_BASE_URL + "/"
else:
self.DNSPOD_API_BASE_URL = DNSPOD_API_BASE_URL
super(DNSPodDns, self).__init__()
def create_dns_record(self, domain_name, domain_dns_value):
self.logger.info("create_dns_record")
# if we have been given a wildcard name, strip wildcard
domain_name = domain_name.lstrip("*.")
subd = ""
if domain_name.count(".") != 1: # not top level domain
pos = domain_name.rfind(".", 0, domain_name.rfind("."))
subd = domain_name[:pos]
domain_name = domain_name[pos + 1 :]
if subd != "":
subd = "." + subd
url = urlparse.urljoin(self.DNSPOD_API_BASE_URL, "Record.Create")
body = {
"record_type": "TXT",
"domain": domain_name,
"sub_domain": "_acme-challenge" + subd,
"value": domain_dns_value,
"record_line_id": "0",
"format": "json",
"login_token": self.DNSPOD_LOGIN,
}
create_dnspod_dns_record_response = requests.post(
url, data=body, timeout=self.HTTP_TIMEOUT
).json()
self.logger.debug(
"create_dnspod_dns_record_response. status_code={0}. response={1}".format(
create_dnspod_dns_record_response["status"]["code"],
create_dnspod_dns_record_response["status"]["message"],
)
)
if create_dnspod_dns_record_response["status"]["code"] != "1":
# raise error so that we do not continue to make calls to ACME
# server
raise ValueError(
"Error creating dnspod dns record: status_code={status_code} response={response}".format(
status_code=create_dnspod_dns_record_response["status"]["code"],
response=create_dnspod_dns_record_response["status"]["message"],
)
)
self.logger.info("create_dns_record_end")
def delete_dns_record(self, domain_name, domain_dns_value):
self.logger.info("delete_dns_record")
domain_name = domain_name.lstrip("*.")
subd = ""
if domain_name.count(".") != 1: # not top level domain
pos = domain_name.rfind(".", 0, domain_name.rfind("."))
subd = domain_name[:pos]
domain_name = domain_name[pos + 1 :]
if subd != "":
subd = "." + subd
url = urllib.parse.urljoin(self.DNSPOD_API_BASE_URL, "Record.List")
# pos = domain_name.rfind(".",0, domain_name.rfind("."))
subdomain = "_acme-challenge." + subd
rootdomain = domain_name
body = {
"login_token": self.DNSPOD_LOGIN,
"format": "json",
"domain": rootdomain,
"subdomain": subdomain,
"record_type": "TXT",
}
list_dns_response = requests.post(url, data=body, timeout=self.HTTP_TIMEOUT).json()
if list_dns_response["status"]["code"] != "1":
self.logger.error(
"list_dns_record_response. status_code={0}. message={1}".format(
list_dns_response["status"]["code"], list_dns_response["status"]["message"]
)
)
for i in range(0, len(list_dns_response["records"])):
rid = list_dns_response["records"][i]["id"]
urlr = urllib.parse.urljoin(self.DNSPOD_API_BASE_URL, "Record.Remove")
bodyr = {
"login_token": self.DNSPOD_LOGIN,
"format": "json",
"domain": rootdomain,
"record_id": rid,
}
delete_dns_record_response = requests.post(
urlr, data=bodyr, timeout=self.HTTP_TIMEOUT
).json()
if delete_dns_record_response["status"]["code"] != "1":
self.logger.error(
"delete_dns_record_response. status_code={0}. message={1}".format(
delete_dns_record_response["status"]["code"],
delete_dns_record_response["status"]["message"],
)
)
self.logger.info("delete_dns_record_success")
try:
import urllib.parse as urlparse
except:
import urlparse
import requests
from . import common
class DuckDNSDns(common.BaseDns):
dns_provider_name = "duckdns"
def __init__(self, duckdns_token, DUCKDNS_API_BASE_URL="https://www.duckdns.org"):
self.duckdns_token = duckdns_token
self.HTTP_TIMEOUT = 65 # seconds
if DUCKDNS_API_BASE_URL[-1] != "/":
self.DUCKDNS_API_BASE_URL = DUCKDNS_API_BASE_URL + "/"
else:
self.DUCKDNS_API_BASE_URL = DUCKDNS_API_BASE_URL
super(DuckDNSDns, self).__init__()
def _common_dns_record(self, logger_info, domain_name, payload_end_arg):
self.logger.info("{0}".format(logger_info))
# if we have been given a wildcard name, strip wildcard
domain_name = domain_name.lstrip("*.")
# add provider domain to the domain name if not present
provider_domain = ".duckdns.org"
if domain_name.rfind(provider_domain) == -1:
"".join((domain_name, provider_domain))
url = urlparse.urljoin(self.DUCKDNS_API_BASE_URL, "update")
payload = dict([("domains", domain_name), ("token", self.duckdns_token), payload_end_arg])
update_duckdns_dns_record_response = requests.get(
url, params=payload, timeout=self.HTTP_TIMEOUT
)
normalized_response = update_duckdns_dns_record_response.text
self.logger.debug(
"update_duckdns_dns_record_response. status_code={0}. response={1}".format(
update_duckdns_dns_record_response.status_code, normalized_response
)
)
if update_duckdns_dns_record_response.status_code != 200 or normalized_response != "OK":
# raise error so that we do not continue to make calls to DuckDNS
# server
raise ValueError(
"Error creating DuckDNS dns record: status_code={status_code} response={response}".format(
status_code=update_duckdns_dns_record_response.status_code,
response=normalized_response,
)
)
self.logger.info("{0}_success".format(logger_info))
def create_dns_record(self, domain_name, domain_dns_value):
self._common_dns_record("create_dns_record", domain_name, ("txt", domain_dns_value))
def delete_dns_record(self, domain_name, domain_dns_value):
self._common_dns_record("delete_dns_record", domain_name, ("clear", "true"))
"""
Hurricane Electric DNS Support
"""
import json
try:
hedns_dependencies = True
import HurricaneDNS as _hurricanedns
except ImportError:
hedns_dependencies = False
from . import common
class _Response(object):
"""
wrapper aliyun resp to the format sewer wanted.
"""
def __init__(self, status_code=200, content=None, headers=None):
self.status_code = status_code
self.headers = headers or {}
self.content = content or {}
self.content = json.dumps(content)
super(_Response, self).__init__()
def json(self):
return json.loads(self.content)
class HurricaneDns(common.BaseDns):
def __init__(self, username, password):
super(HurricaneDns, self).__init__()
if not hedns_dependencies:
raise ImportError(
"""You need to install HurricaneDns dependencies. run: pip3 install sewer[hurricane]"""
)
self.clt = _hurricanedns.HurricaneDNS(username, password)
@staticmethod
def extract_zone(domain_name):
"""
extract domain to root, sub, acme_txt
:param str domain_name: the value sewer client passed in, like *.menduo.example.com
:return tuple: root, zone, acme_txt
"""
# if we have been given a wildcard name, strip wildcard
domain_name = domain_name.lstrip("*.")
if domain_name.count(".") > 1:
zone, middle, last = str(domain_name).rsplit(".", 2)
root = ".".join([middle, last])
acme_txt = "_acme-challenge.%s" % zone
else:
zone = ""
root = domain_name
acme_txt = "_acme-challenge"
return root, zone, acme_txt
def create_dns_record(self, domain_name, domain_dns_value):
self.logger.info("create_dns_record start: %s", (domain_name, domain_dns_value))
root, _, acme_txt = self.extract_zone(domain_name)
self.clt.add_record(root, acme_txt, "TXT", domain_dns_value, ttl=300)
self.logger.info("create_dns_record end: %s", (domain_name, domain_dns_value))
def delete_dns_record(self, domain_name, domain_dns_value):
self.logger.info("delete_dns_record start: %s", (domain_name, domain_dns_value))
root, _, acme_txt = self.extract_zone(domain_name)
host = "%s.%s" % (acme_txt, root)
recored_list = self.clt.get_records(root, host, "TXT")
for i in recored_list:
self.clt.del_record(root, i["id"])
self.logger.info("delete_dns_record end: %s", (domain_name, domain_dns_value))
This diff is collapsed.
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