Initial YakPanel commit

This commit is contained in:
Niranjan
2026-04-07 02:04:22 +05:30
commit 2826d3e7f3
5359 changed files with 1390724 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
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

View File

@@ -0,0 +1,75 @@
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-model model 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-model doesn't support this
self.logger.info("delete_dns_record_success")

View File

@@ -0,0 +1,210 @@
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 model 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 model 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 model 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

View File

@@ -0,0 +1,100 @@
# 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

View File

@@ -0,0 +1,155 @@
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 model 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 model 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")

View File

@@ -0,0 +1,77 @@
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 model TXT record for a domain/subdomain name on
a chosen DNS provider.
:param domain_name: :string: The domain/subdomain name whose model 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 model client like dig(https://linux.die.net/man/1/dig) to do a model 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 model client like: https://toolbox.googleapps.com/apps/dig/#TXT/
Please consult your model 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 model TXT record for a domain/subdomain name on
a chosen DNS provider.
:param domain_name: :string: The domain/subdomain name whose model 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.")

View File

@@ -0,0 +1,121 @@
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 model 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")

View File

@@ -0,0 +1,63 @@
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 model 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"))

View File

@@ -0,0 +1,79 @@
"""
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))

View File

@@ -0,0 +1,242 @@
try:
import urllib.parse as urlparse
except:
import urlparse
import requests
from . import common
try:
rackspace_dependencies = True
import tldextract
except ImportError:
rackspace_dependencies = False
import time
class RackspaceDns(common.BaseDns):
"""
"""
dns_providername = "rackspace"
def get_rackspace_credentials(self):
self.logger.debug("get_rackspace_credentials")
RACKSPACE_IDENTITY_URL = "https://identity.api.rackspacecloud.com/v2.0/tokens"
payload = {
"auth": {
"RAX-KSKEY:apiKeyCredentials": {
"username": self.RACKSPACE_USERNAME,
"apiKey": self.RACKSPACE_API_KEY,
}
}
}
find_rackspace_api_details_response = requests.post(RACKSPACE_IDENTITY_URL, json=payload)
self.logger.debug(
"find_rackspace_api_details_response. status_code={0}".format(
find_rackspace_api_details_response.status_code
)
)
if find_rackspace_api_details_response.status_code != 200:
raise ValueError(
"Error getting token and URL details from rackspace identity server: status_code={status_code} response={response}".format(
status_code=find_rackspace_api_details_response.status_code,
response=self.log_response(find_rackspace_api_details_response),
)
)
data = find_rackspace_api_details_response.json()
api_token = data["access"]["token"]["id"]
url_data = next(
(item for item in data["access"]["serviceCatalog"] if item["type"] == "rax:model"), None
)
if url_data is None:
raise ValueError(
"Error finding url data for the rackspace model api in the response from the identity server"
)
else:
api_base_url = url_data["endpoints"][0]["publicURL"] + "/"
return (api_token, api_base_url)
def __init__(self, RACKSPACE_USERNAME, RACKSPACE_API_KEY):
if not rackspace_dependencies:
raise ImportError(
"""You need to install RackspaceDns dependencies. run; pip3 install sewer[rackspace]"""
)
self.RACKSPACE_DNS_ZONE_ID = None
self.RACKSPACE_USERNAME = RACKSPACE_USERNAME
self.RACKSPACE_API_KEY = RACKSPACE_API_KEY
self.HTTP_TIMEOUT = 65 # seconds
super(RackspaceDns, self).__init__()
self.RACKSPACE_API_TOKEN, self.RACKSPACE_API_BASE_URL = self.get_rackspace_credentials()
self.RACKSPACE_HEADERS = {
"X-Auth-Token": self.RACKSPACE_API_TOKEN,
"Content-Type": "application/json",
}
def get_dns_zone(self, domain_name):
self.logger.debug("get_dns_zone")
extracted_domain = tldextract.extract(domain_name)
self.RACKSPACE_DNS_ZONE = ".".join([extracted_domain.domain, extracted_domain.suffix])
def find_dns_zone_id(self, domain_name):
self.logger.debug("find_dns_zone_id")
self.get_dns_zone(domain_name)
url = self.RACKSPACE_API_BASE_URL + "domains"
find_dns_zone_id_response = requests.get(url, headers=self.RACKSPACE_HEADERS)
self.logger.debug(
"find_dns_zone_id_response. status_code={0}".format(
find_dns_zone_id_response.status_code
)
)
if find_dns_zone_id_response.status_code != 200:
raise ValueError(
"Error getting rackspace model domain info: status_code={status_code} response={response}".format(
status_code=find_dns_zone_id_response.status_code,
response=self.log_response(find_dns_zone_id_response),
)
)
result = find_dns_zone_id_response.json()
domain_data = next(
(item for item in result["domains"] if item["name"] == self.RACKSPACE_DNS_ZONE), None
)
if domain_data is None:
raise ValueError(
"Error finding information for {dns_zone} in model response data:\n{response_data})".format(
dns_zone=self.RACKSPACE_DNS_ZONE,
response_data=self.log_response(find_dns_zone_id_response),
)
)
dns_zone_id = domain_data["id"]
self.logger.debug("find_dns_zone_id_success")
return dns_zone_id
def find_dns_record_id(self, domain_name, domain_dns_value):
self.logger.debug("find_dns_record_id")
self.RACKSPACE_DNS_ZONE_ID = self.find_dns_zone_id(domain_name)
url = self.RACKSPACE_API_BASE_URL + "domains/{0}/records".format(self.RACKSPACE_DNS_ZONE_ID)
find_dns_record_id_response = requests.get(url, headers=self.RACKSPACE_HEADERS)
self.logger.debug(
"find_dns_record_id_response. status_code={0}".format(
find_dns_record_id_response.status_code
)
)
self.logger.debug(url)
if find_dns_record_id_response.status_code != 200:
raise ValueError(
"Error finding model records for {dns_zone}: status_code={status_code} response={response}".format(
dns_zone=self.RACKSPACE_DNS_ZONE,
status_code=find_dns_record_id_response.status_code,
response=self.log_response(find_dns_record_id_response),
)
)
records = find_dns_record_id_response.json()["records"]
RACKSPACE_RECORD_DATA = next(
(item for item in records if item["data"] == domain_dns_value), None
)
if RACKSPACE_RECORD_DATA is None:
raise ValueError(
"Couldn't find record with name {domain_name}\ncontaining data: {domain_dns_value}\nin the response data:{response_data}".format(
domain_name=domain_name,
domain_dns_value=domain_dns_value,
response_data=self.log_response(find_dns_record_id_response),
)
)
record_id = RACKSPACE_RECORD_DATA["id"]
self.logger.debug("find_dns_record_id success")
return record_id
def poll_callback_url(self, callback_url):
start_time = time.time()
while True:
callback_url_response = requests.get(callback_url, headers=self.RACKSPACE_HEADERS)
if time.time() > start_time + self.HTTP_TIMEOUT:
raise ValueError(
"Timed out polling callbackurl for model record status. Last status_code={status_code} last response={response}".format(
status_code=callback_url_response.status_code,
response=self.log_response(callback_url_response),
)
)
if callback_url_response.status_code != 200:
raise Exception(
"Could not get model record status from callback url. Status code ={status_code}. response={response}".format(
status_code=callback_url_response.status_code,
response=self.log_response(callback_url_response),
)
)
if callback_url_response.json()["status"] == "ERROR":
raise Exception(
"Error in creating/deleting model record: status_Code={status_code}. response={response}".format(
status_code=callback_url_response.status_code,
response=self.log_response(callback_url_response),
)
)
if callback_url_response.json()["status"] == "COMPLETED":
break
def create_dns_record(self, domain_name, domain_dns_value):
self.logger.info("create_dns_record")
# strip wildcard if present
domain_name = domain_name.lstrip("*.")
self.RACKSPACE_DNS_ZONE_ID = self.find_dns_zone_id(domain_name)
record_name = "_acme-challenge." + domain_name
url = urlparse.urljoin(
self.RACKSPACE_API_BASE_URL, "domains/{0}/records".format(self.RACKSPACE_DNS_ZONE_ID)
)
body = {
"records": [{"name": record_name, "type": "TXT", "data": domain_dns_value, "ttl": 3600}]
}
create_rackspace_dns_record_response = requests.post(
url, headers=self.RACKSPACE_HEADERS, json=body, timeout=self.HTTP_TIMEOUT
)
self.logger.debug(
"create_rackspace_dns_record_response. status_code={status_code}".format(
status_code=create_rackspace_dns_record_response.status_code
)
)
if create_rackspace_dns_record_response.status_code != 202:
raise ValueError(
"Error creating rackspace model record: status_code={status_code} response={response}".format(
status_code=create_rackspace_dns_record_response.status_code,
response=create_rackspace_dns_record_response.text,
)
)
# response=self.log_response(create_rackspace_dns_record_response)))
# After posting the model record we want created, the response gives us a url to check that will
# update when the job is done
callback_url = create_rackspace_dns_record_response.json()["callbackUrl"]
self.poll_callback_url(callback_url)
self.logger.info(
"create_dns_record_success. Name: {record_name} Data: {data}".format(
record_name=record_name, data=domain_dns_value
)
)
def delete_dns_record(self, domain_name, domain_dns_value):
self.logger.info("delete_dns_record")
record_name = "_acme-challenge." + domain_name
self.RACKSPACE_DNS_ZONE_ID = self.find_dns_zone_id(domain_name)
self.RACKSPACE_RECORD_ID = self.find_dns_record_id(domain_name, domain_dns_value)
url = self.RACKSPACE_API_BASE_URL + "domains/{domain_id}/records/?id={record_id}".format(
domain_id=self.RACKSPACE_DNS_ZONE_ID, record_id=self.RACKSPACE_RECORD_ID
)
delete_dns_record_response = requests.delete(url, headers=self.RACKSPACE_HEADERS)
# After sending a delete request, if all goes well, we get a 202 from the server and a URL that we can poll
# to see when the job is done
self.logger.debug(
"delete_dns_record_response={0}".format(delete_dns_record_response.status_code)
)
if delete_dns_record_response.status_code != 202:
raise ValueError(
"Error deleting rackspace model record: status_code={status_code} response={response}".format(
status_code=delete_dns_record_response.status_code,
response=self.log_response(delete_dns_record_response),
)
)
callback_url = delete_dns_record_response.json()["callbackUrl"]
self.poll_callback_url(callback_url)
self.logger.info(
"delete_dns_record_success. Name: {record_name} Data: {data}".format(
record_name=record_name, data=domain_dns_value
)
)