Initial project commit

This commit is contained in:
2021-08-28 11:28:23 +12:00
commit af437cfae5
22 changed files with 3442 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.db
venv/
.venv
.idea

11
Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM pypy:slim-buster
RUN mkdir -p /opt/apikeyhandler/config
VOLUME /opt/apikeyhandler/config
COPY ./src/ /opt/apikeyhandler
WORKDIR /opt/apikeyhandler
RUN pip install -r requirements.txt
CMD pypy3 main.py

16
Dockerfile.new Normal file
View File

@@ -0,0 +1,16 @@
FROM centos:latest
ENV APP_NAME=rpmbuild
ENV VERSION=latest
RUN mkdir -p /tmp/build/rpm
WORKDIR /tmp/build/rpm
RUN dnf install -y --allowerasing gcc rpm-build rpm-devel \
rpmlint make bash coreutils \
diffutils patch rpmdevtools && \
dnf clean all && \
rm -Rf /var/dnf/cache && \
rpmdev-setuptree
VOLUME /tmp/build/rpm
CMD ["rpmbuild", "--define version ${VERSION}", "-bb", "${APP_NAME}.spec"]

50
Dockerfile.scratch Normal file
View File

@@ -0,0 +1,50 @@
FROM python:3.7.9 as builder
# Allow Passing Version from CI
ARG VERSION
ENV LC_ALL=en_NZ.utf8
ENV LANG=en_NZ.utf8
ENV APP_NAME="apikeyauthhandler"
RUN mkdir -p /tmp/build && apt-get update && \
apt-get install -y libgcc1-dbg
COPY src/ /tmp/build/
COPY requirements.txt /tmp/build
WORKDIR /tmp/build
WORKDIR /tmp/src
RUN wget https://github.com/NixOS/patchelf/releases/download/0.12/patchelf-0.12.tar.bz2 && \
tar xvf patchelf-0.12.tar.bz2 && \
cd /tmp/src/patchelf-0.12* && \
./configure --prefix="/usr" && \
make install
WORKDIR /tmp/build
RUN pip3 install -r requirements.txt && \
pyinstaller --hidden-import=json \
--hidden-import=jaraco \
--noconfirm --onefile ${APP_NAME}.py && \
cd /tmp/build/dist && \
staticx ${APP_NAME} ./${APP_NAME}_static
RUN mkdir -p /tmp/approot && \
mkdir -p /tmp/approot/app && \
mkdir -p /tmp/approot/app/config && \
mkdir -p /tmp/approot/etc && \
mkdir -p /tmp/approot/tmp && \
mkdir -p /tmp/approot/data && \
mkdir -p /tmp/approot/lib/x86_64-linux-gnu && \
cp /tmp/build/config/app.yml /tmp/approot/app/config/app.yml && \
cp /tmp/build/dist/${APP_NAME}_static /tmp/approot/app/${APP_NAME} && \
cp /usr/lib/gcc/x86_64-linux-gnu/8/libgcc_s.so.1 /tmp/approot/lib/x86_64-linux-gnu/libgcc_s.so.1
FROM scratch
COPY --from=builder /tmp/approot /
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
ENV TZ=Pacific/Auckland
WORKDIR /app
VOLUME /app/config /data
CMD ["/app/apikeyauthhandler"]

14
Pipfile Normal file
View File

@@ -0,0 +1,14 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
cherrypy = "==18.6.0"
sqlalchemy = "==1.3.19"
pyyaml = "==5.3.1"
[requires]
python_version = "3.8"

188
Pipfile.lock generated Normal file
View File

@@ -0,0 +1,188 @@
{
"_meta": {
"hash": {
"sha256": "046c8d93fda36d9f83c939c4d6474b700962db5b1e4cb774c4ea5b76f0af50af"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.8"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"cheroot": {
"hashes": [
"sha256:ab342666c8e565a55cd2baf2648be9b379269a89d47e60862a087cff9d8b33ce",
"sha256:b6c18caf5f79cdae668c35fc8309fc88ea4a964cce9e2ca8504fab13bcf57301"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==8.4.5"
},
"cherrypy": {
"hashes": [
"sha256:56608edd831ad00991ae585625e0206ed61cf1a0850e4b2cc48489fb2308c499",
"sha256:c0a7283f02a384c112a0a18404fd3abd849fc7fd4bec19378067150a2573d2e4"
],
"index": "pypi",
"version": "==18.6.0"
},
"jaraco.classes": {
"hashes": [
"sha256:116429c2047953f525afdcae165475c4589c7b14870e78b2d068ecb01018827e",
"sha256:c38698ff8ef932eb33d91c0e8fc192ad7c44ecee03f7f585afd4f35aeaef7aab"
],
"markers": "python_version >= '3.6'",
"version": "==3.1.0"
},
"jaraco.collections": {
"hashes": [
"sha256:a7889f28c80c4875bd6256d9924e8526dacfef22cd7b80ff8469b4d312f9f144",
"sha256:be570ef4f2e7290b757449395238fa63d70a9255574624e73c5ff9f1ee554721"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.0"
},
"jaraco.functools": {
"hashes": [
"sha256:9fedc4be3117512ca3e03e1b2ffa7a6a6ffa589bfb7d02bfb324e55d493b94f4",
"sha256:d3dc9f6c1a1d45d7f59682a3bf77aceb685c1a60891606c7e4161e72ecc399ad"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.1"
},
"jaraco.text": {
"hashes": [
"sha256:c87569c9afae14f71b2e1c57f316770ab6981ab675d9c602be1c7981161bacdd",
"sha256:e5078b1126cc0f166c7859aa75103a56c0d0f39ebcafc21695615472e0f810ec"
],
"markers": "python_version >= '2.7'",
"version": "==3.2.0"
},
"more-itertools": {
"hashes": [
"sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20",
"sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c"
],
"markers": "python_version >= '3.5'",
"version": "==8.5.0"
},
"portend": {
"hashes": [
"sha256:600dd54175e17e9347e5f3d4217aa8bcf4bf4fa5ffbc4df034e5ec1ba7cdaff5",
"sha256:62dd00b94a6a55fbf0320365fbdeba37f0d1fe14d613841037dc4780bedfda8f"
],
"markers": "python_version >= '2.7'",
"version": "==2.6"
},
"pytz": {
"hashes": [
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
],
"version": "==2020.1"
},
"pywin32": {
"hashes": [
"sha256:00eaf43dbd05ba6a9b0080c77e161e0b7a601f9a3f660727a952e40140537de7",
"sha256:11cb6610efc2f078c9e6d8f5d0f957620c333f4b23466931a247fb945ed35e89",
"sha256:1f45db18af5d36195447b2cffacd182fe2d296849ba0aecdab24d3852fbf3f80",
"sha256:37dc9935f6a383cc744315ae0c2882ba1768d9b06700a70f35dc1ce73cd4ba9c",
"sha256:6e38c44097a834a4707c1b63efa9c2435f5a42afabff634a17f563bc478dfcc8",
"sha256:8319bafdcd90b7202c50d6014efdfe4fde9311b3ff15fd6f893a45c0868de203",
"sha256:9b3466083f8271e1a5eb0329f4e0d61925d46b40b195a33413e0905dccb285e8",
"sha256:a60d795c6590a5b6baeacd16c583d91cce8038f959bd80c53bd9a68f40130f2d",
"sha256:af40887b6fc200eafe4d7742c48417529a8702dcc1a60bf89eee152d1d11209f",
"sha256:ec16d44b49b5f34e99eb97cf270806fdc560dff6f84d281eb2fcb89a014a56a9",
"sha256:ed74b72d8059a6606f64842e7917aeee99159ebd6b8d6261c518d002837be298",
"sha256:fa6ba028909cfc64ce9e24bcf22f588b14871980d9787f1e2002c99af8f1850c"
],
"markers": "sys_platform == 'win32'",
"version": "==228"
},
"pyyaml": {
"hashes": [
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
"sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
"sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
"sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
"sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
],
"index": "pypi",
"version": "==5.3.1"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0"
},
"sqlalchemy": {
"hashes": [
"sha256:072766c3bd09294d716b2d114d46ffc5ccf8ea0b714a4e1c48253014b771c6bb",
"sha256:107d4af989831d7b091e382d192955679ec07a9209996bf8090f1f539ffc5804",
"sha256:15c0bcd3c14f4086701c33a9e87e2c7ceb3bcb4a246cd88ec54a49cf2a5bd1a6",
"sha256:26c5ca9d09f0e21b8671a32f7d83caad5be1f6ff45eef5ec2f6fd0db85fc5dc0",
"sha256:276936d41111a501cf4a1a0543e25449108d87e9f8c94714f7660eaea89ae5fe",
"sha256:3292a28344922415f939ee7f4fc0c186f3d5a0bf02192ceabd4f1129d71b08de",
"sha256:33d29ae8f1dc7c75b191bb6833f55a19c932514b9b5ce8c3ab9bc3047da5db36",
"sha256:3bba2e9fbedb0511769780fe1d63007081008c5c2d7d715e91858c94dbaa260e",
"sha256:465c999ef30b1c7525f81330184121521418a67189053bcf585824d833c05b66",
"sha256:51064ee7938526bab92acd049d41a1dc797422256086b39c08bafeffb9d304c6",
"sha256:5a49e8473b1ab1228302ed27365ea0fadd4bf44bc0f9e73fe38e10fdd3d6b4fc",
"sha256:618db68745682f64cedc96ca93707805d1f3a031747b5a0d8e150cfd5055ae4d",
"sha256:6547b27698b5b3bbfc5210233bd9523de849b2bb8a0329cd754c9308fc8a05ce",
"sha256:6557af9e0d23f46b8cd56f8af08eaac72d2e3c632ac8d5cf4e20215a8dca7cea",
"sha256:73a40d4fcd35fdedce07b5885905753d5d4edf413fbe53544dd871f27d48bd4f",
"sha256:8280f9dae4adb5889ce0bb3ec6a541bf05434db5f9ab7673078c00713d148365",
"sha256:83469ad15262402b0e0974e612546bc0b05f379b5aa9072ebf66d0f8fef16bea",
"sha256:860d0fe234922fd5552b7f807fbb039e3e7ca58c18c8d38aa0d0a95ddf4f6c23",
"sha256:883c9fb62cebd1e7126dd683222b3b919657590c3e2db33bdc50ebbad53e0338",
"sha256:8afcb6f4064d234a43fea108859942d9795c4060ed0fbd9082b0f280181a15c1",
"sha256:96f51489ac187f4bab588cf51f9ff2d40b6d170ac9a4270ffaed535c8404256b",
"sha256:9e865835e36dfbb1873b65e722ea627c096c11b05f796831e3a9b542926e979e",
"sha256:aa0554495fe06172b550098909be8db79b5accdf6ffb59611900bea345df5eba",
"sha256:b595e71c51657f9ee3235db8b53d0b57c09eee74dfb5b77edff0e46d2218dc02",
"sha256:b6ff91356354b7ff3bd208adcf875056d3d886ed7cef90c571aef2ab8a554b12",
"sha256:b70bad2f1a5bd3460746c3fb3ab69e4e0eb5f59d977a23f9b66e5bdc74d97b86",
"sha256:c7adb1f69a80573698c2def5ead584138ca00fff4ad9785a4b0b2bf927ba308d",
"sha256:c898b3ebcc9eae7b36bd0b4bbbafce2d8076680f6868bcbacee2d39a7a9726a7",
"sha256:e49947d583fe4d29af528677e4f0aa21f5e535ca2ae69c48270ebebd0d8843c0",
"sha256:eb1d71643e4154398b02e88a42fc8b29db8c44ce4134cf0f4474bfc5cb5d4dac",
"sha256:f2e8a9c0c8813a468aa659a01af6592f71cd30237ec27c4cc0683f089f90dcfc",
"sha256:fe7fe11019fc3e6600819775a7d55abc5446dda07e9795f5954fdbf8a49e1c37"
],
"index": "pypi",
"version": "==1.3.19"
},
"tempora": {
"hashes": [
"sha256:599a3a910b377f2b544c7b221582ecf4cb049b017c994b37f2b1a9ed1099716e",
"sha256:9f46de767be7dd21d9602a8a5b0978fd55abc70af3e2a7814c85c00d7a8fffa3"
],
"markers": "python_version >= '3.6'",
"version": "==4.0.0"
},
"zc.lockfile": {
"hashes": [
"sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b",
"sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f"
],
"version": "==2.0"
}
},
"develop": {}
}

105
app.py Normal file
View File

@@ -0,0 +1,105 @@
from flask import Flask, request
import mmap
import re
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
@app.route('/CMD_API_LOGIN_TEST')
def login_test():
multi_dict = request.values
for key in multi_dict:
print(multi_dict.get(key))
print(multi_dict.getlist(key))
# print(request.values)
print(request.headers)
print(request.authorization)
return 'error=0&text=Login OK&details=none'
@app.route('/CMD_API_DNS_ADMIN', methods=['GET', 'POST'])
def domain_admin():
print(str(request.data, encoding="utf-8"))
print(request.values.get('action'))
action = request.values.get('action')
if action == 'exists':
# DirectAdmin is checking whether the domain is in the cluster
return 'result: exists=1'
if action == 'delete':
# Domain is being removed from the DNS
hostname = request.values.get('hostname')
username = request.values.get('username')
domain = request.values.get('select0')
if action == 'rawsave':
# DirectAdmin wants to add/update a domain
hostname = request.values.get('hostname')
username = request.values.get('username')
domain = request.values.get('domain')
if not check_zone_exists(str(domain)):
put_zone_index(str(domain))
write_zone_file(str(domain), request.data.decode("utf-8"))
else:
# Domain already exists
write_zone_file(str(domain), request.data.decode("utf-8"))
def create_zone_index():
# Create an index of all zones present from zone definitions
regex = r"(?<=\")(?P<domain>.*)(?=\"\s)"
with open(zone_index_file, 'w+') as f:
with open(named_conf, 'r') as named_file:
while True:
# read line
line = named_file.readline()
if not line:
# Reached end of file
break
print(line)
hosted_domain = re.search(regex, line).group(0)
f.write(hosted_domain + "\n")
def put_zone_index(zone_name):
# add a new zone to index
with open(zone_index_file, 'a+') as f:
# We are using append mode
f.write(zone_name)
def write_zone_file(zone_name, data):
# Write the zone to file
with open(zones_dir + '/' + zone_name + '.db', 'w') as f:
f.write(data)
def check_zone_exists(zone_name):
# Check if zone is present in the index
with open(zone_index_file, 'r') as f:
try:
s = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
if s.find(bytes(zone_name, encoding='utf8')) != -1:
return True
else:
return False
except ValueError as e:
# File Empty?
return False
if __name__ == '__main__':
zones_dir = "/etc/pdns/zones"
zone_index_file = "/etc/pdns/zones/.index"
named_conf = "/etc/pdns/named.conf"
create_zone_index()
app.run(host="0.0.0.0")

48
docker-compose.yml Normal file
View File

@@ -0,0 +1,48 @@
version: '3.7'
services:
app:
image: registry.dockerprod.ultrafast.co.nz/uff/apikeyhandler:0.10
networks:
- traefik-net
volumes:
- /etc/localtime:/etc/localtime:ro # Mount Timezone config to container
- /data/swarm-vols/apikeyhandler:/opt/apikeyhandler/config # Store Config on Persistent drive shared between nodes
deploy:
mode: replicated
replicas: 1
placement:
constraints:
- node.role == worker # Place this service on Worker Nodes alternatively may specify manager if you want service on manager node.
labels:
- "traefik.http.routers.apikeyauth.rule=Host(`apiauth-internal.dockertest.ultrafast.co.nz`)" # This label creates a route Traefik will listen on
- "traefik.http.routers.apikeyauth.tls=true" # Enable TLS, in this example using default TLS cert
- "traefik.http.services.apikeyauth.loadbalancer.server.port=8080" # Set Port to proxy
- "traefik.enable=true" # This flag enables load balancing through Traefik :)
- "traefik.docker.network=traefik-net" # Set the network to connect to container on
- "traefik.http.middlewares.apikeyauth.forwardauth.address=https://apiauth-internal.dockertest.ultrafast.co.nz"
- "traefik.http.middlewares.apikeyauth.forwardauth.trustForwardHeader=true"
- "traefik.http.middlewares.apikeyauth.forwardauth.authResponseHeaders=X-Client-Id"
- "traefik.http.middlewares.apikeyauth.forwardauth.tls.insecureSkipVerify=true"
test_app:
image: containous/whoami
networks:
- traefik-net
volumes:
- /etc/localtime:/etc/localtime:ro # Mount Timezone config to container
deploy:
mode: replicated
replicas: 1
placement:
constraints:
- node.role == worker # Place this service on Worker Nodes alternatively may specify manager if you want service on manager node.
labels:
- "traefik.http.routers.testapp.rule=Host(`testapp.dockertest.ultrafast.co.nz`)" # This label creates a route Traefik will listen on
- "traefik.http.routers.testapp.tls=true" # Enable TLS, in this example using default TLS cert
- "traefik.http.routers.testapp.middlewares=apikeyauth"
- "traefik.http.services.testapp.loadbalancer.server.port=80" # Set Port to proxy
- "traefik.enable=true" # This flag enables load balancing through Traefik :)
- "traefik.docker.network=traefik-net" # Set the network to connect to container on
networks:
traefik-net:
external: true

0
entrypoint.sh Normal file
View File

8
requirements.txt Normal file
View File

@@ -0,0 +1,8 @@
cherrypy==18.6.1
pyyaml==5.3.1
python-json-logger
sqlalchemy==1.3.20
pyinstaller==4.0
patchelf-wrapper
staticx

0
src/__init__.py Normal file
View File

8
src/config/app.yml Normal file
View File

@@ -0,0 +1,8 @@
environment: ''
proxy_support: true
log_level: debug
log_to: file
proxy_support_base: http://127.0.0.1
server_port: 2222
token_valid_for_days: 30
timezone: Pacific/Auckland

View File

@@ -0,0 +1,16 @@
---
# Port that the server will run on.
server_port: 8080
# Turn Proxy support on. True if behind Traefik/Nginx for example
proxy_support: True
# Proxy support Base URI
proxy_support_base: http://127.0.0.1
# Values accepted are '', production, staging
environment: ''
api_keys:
- key: ZU7f3NogDxIzhfW5tsv9
name: super # Name of user
expires: never # Expiry never for superuser
- key: DhCIZ5yKjVN7ReKVh6Tl
name: jaydeep
expires: 21/11/2020, 10:56:24

2590
src/config/directdns.log Normal file

File diff suppressed because it is too large Load Diff

67
src/db.py Normal file
View File

@@ -0,0 +1,67 @@
import re
from sqlalchemy import create_engine
def connect(hostname, sid, username, password, port=1521) -> object:
host = hostname
port = port
sid = sid
user = username
password = password
sid = cx_Oracle.makedsn(host, port, sid=sid)
connection_string = 'oracle://{user}:{password}@{sid}'.format(
user=user,
password=password,
sid=sid
)
engine = create_engine(
connection_string,
convert_unicode=False,
pool_recycle=10,
pool_size=50,
)
return engine
def make_address(flt_num: str, street_name: str, area: str, address_string: str) -> str:
"""
Take input parameters from GIS Data and returns Address as string
:rtype: str
"""
street_number = nv(flt_num).upper().strip() # Make the street number upper case and strip whitespace
street_name = nv(street_name).upper().strip() # Make the street name upper case and strip whitespace
town_name = nv(area)
if area != 'None' and \
area is not None and \
area != '':
town_name = nv(area).upper().strip() # Make the area upper case and strip whitespace
else:
town_name = nv(lookup_town_in_string(address_string)) # Get the town from address string
# Assemble the address string
full_address = street_number + " " + street_name + " " + town_name
return full_address
def lookup_town_in_string(address: str) -> str:
for p in street_type_lookup():
first_word = r"^(\w+)\s?((?!\\1)([\w]+)?)(?:\s+[\d]{4})" # Return First Words
try:
f = address.index(p)
size = len(p)
if f is not None:
m = re.search(first_word, address[f + size::].strip())
if m.group(1) is not None and m.group(2) is not None:
if m.group(1) != m.group(2):
return m.group(1) + ' ' + m.group(2)
else:
return m.group(1)
elif m.group(1) is not None and m.group(2) is None:
return m.group(1)
except ValueError:
pass

232
src/directdnsonly.py Normal file
View File

@@ -0,0 +1,232 @@
import mmap
import cherrypy
from cherrypy import request
from cherrypy._cpnative_server import CPHTTPServer
from pythonjsonlogger import jsonlogger
import logging
import os
import time
import sys
import yaml
import datetime
import lib.common
import lib.db
import lib.db.models
class DaDNS(object):
@cherrypy.expose
def CMD_API_LOGIN_TEST(self, **params):
return 'error=0&text=Login OK&details=none'
@cherrypy.expose
def CMD_API_DNS_ADMIN(self, **params):
applog.debug('Processing Method: ' + request.method)
if request.method == 'POST':
action = request.params.get('action')
decoded_params = None
if action is None:
decoded_params = decode_params(str(request.body.read(), 'utf-8'))
action = decoded_params['action']
zone_file = str(request.body.read(), 'utf-8')
applog.debug(zone_file)
if action == 'delete':
# Domain is being removed from the DNS
hostname = decoded_params['hostname']
domain = decoded_params['select0']
record = session.query(lib.db.models.Domain).filter_by(domain=domain).one()
if record.hostname == hostname:
applog.debug('Hostname matches the original host {}: Delete is allowed'.format('hostname'))
session.delete(record)
applog.info('{} deleted from database')
write_named_include()
if action == 'rawsave':
# DirectAdmin wants to add/update a domain
hostname = request.params.get('hostname')
username = request.params.get('username')
domain = request.params.get('domain')
applog.debug('Domain name to check: ' + domain)
applog.debug('Does zone exist? ' + str(check_zone_exists(str(domain))))
if not check_zone_exists(str(domain)):
applog.debug('Zone is not present in db')
put_zone_index(str(domain), str(hostname), str(username))
write_zone_file(str(domain), zone_file)
else:
# Domain already exists
applog.debug('Zone is present in db')
write_zone_file(str(domain), zone_file)
elif request.method == 'GET':
applog.debug('Action Type: ' + request.params.get('action'))
action = request.params.get('action')
if action == 'exists':
# DirectAdmin is checking whether the domain is in the cluster
if check_zone_exists(request.params.get('domain')):
return 'result: exists=1'
else:
return 'result: exists=0'
def create_zone_index():
# Create an index of all zones present from zone definitions
regex = r"(?<=\")(?P<domain>.*)(?=\"\s)"
with open(zone_index_file, 'w+') as f:
with open(named_conf, 'r') as named_file:
while True:
# read line
line = named_file.readline()
if not line:
# Reached end of file
break
print(line)
hosted_domain = re.search(regex, line).group(0)
f.write(hosted_domain + "\n")
def put_zone_index(zone_name, host_name, user_name):
# add a new zone to index
applog.debug('Placed zone into database.. {}'.format(str(zone_name)))
domain = lib.db.models.Domain(domain=zone_name, hostname=host_name, username=user_name)
session.add(domain)
session.commit()
def write_zone_file(zone_name, data):
# Write the zone to file
applog.debug('Zone Name for write: ' + zone_name)
applog.debug('Zone file to write: \n' + data)
with open(zones_dir + '/' + zone_name + '.db', 'w') as f:
f.write(data)
applog.debug('Zone written to {}'.format(zones_dir + '/' + zone_name + '.db'))
def write_named_include():
applog.debug('Rewrite named zone include...')
domains = session.query(lib.db.models.Domain).all()
with open(named_conf, 'w') as f:
for domain in domains:
applog.debug('Writing zone {} to named.config'.format(domain.domain))
f.write('zone "{}" { type master; file "/etc/pdns/zones/{}.db"; };'
.format(domain.domain,
domain.domain))
def check_parent_domain_owner(zone_name, owner):
applog.debug('Checking if {} is owner of parent in the DB'.format(zone_name))
# check try to find domain name
parent_domain = ".".join(zone_name.split('.')[1:])
domain_exists = session.query(session.query(lib.db.models.Domain).filter_by(domain=parent_domain).exists()).scalar()
if domain_exists:
# domain exists in the db
applog.debug('{} exists in db'.format(parent_domain))
domain_record = session.query(lib.db.models.Domain).filter_by(domain=parent_domain).one()
applog.debug(str(domain_record))
if domain_record.username == owner:
return True
else:
return False
def check_zone_exists(zone_name):
# Check if zone is present in the index
applog.debug('Checking if {} is present in the DB'.format(zone_name))
domain_exists = session.query(session.query(lib.db.models.Domain).filter_by(domain=zone_name).exists()).scalar()
if domain_exists:
return True
else:
return False
def decode_params(payload):
from urllib.parse import parse_qs
response = parse_qs(payload)
params = dict()
for key, val in response.items():
params[key] = val[0]
return params
@cherrypy.expose
@cherrypy.tools.json_out()
def health(self):
# Defaults to 200
return {"Message": "OK!"}
def setup_logging():
os.environ['TZ'] = config['timezone']
time.tzset()
applog = logging.getLogger()
applog.setLevel(level=getattr(logging, config['log_level'].upper()))
if config['log_to'] == 'stdout':
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(level=getattr(logging, config['log_level'].upper()))
formatter = jsonlogger.JsonFormatter(
fmt='%(asctime)s %(levelname)s %(message)s'
)
handler.setFormatter(formatter)
applog.addHandler(handler)
elif config['log_to'] == 'file':
handler = logging.FileHandler('./config/directdns.log')
handler.setLevel(level=getattr(logging, config['log_level'].upper()))
formatter = jsonlogger.JsonFormatter(
fmt='%(asctime)s %(levelname)s %(message)s'
)
handler.setFormatter(formatter)
applog.addHandler(handler)
return applog
if __name__ == '__main__':
app_version = "1.0.0"
if os.path.isfile("/lib/x86_64-linux-gnu/" + "libgcc_s.so.1"):
# Load local library
libgcc_s = ctypes.cdll.LoadLibrary("/lib/x86_64-linux-gnu/" + "libgcc_s.so.1")
# We are about to start our application
with open(r'config/app.yml') as config_file:
config = yaml.load(config_file, Loader=yaml.SafeLoader)
applog = setup_logging()
applog.info('DirectDNS Starting')
applog.info('Timezone is {}'.format(config['timezone']))
applog.info('Get Database Connection')
session = lib.db.connect()
applog.info('Database Connected!')
zones_dir = "/etc/pdns/zones"
named_conf = "/etc/pdns/named.conf"
cherrypy.__version__ = ''
cherrypy._cperror._HTTPErrorTemplate = cherrypy._cperror._HTTPErrorTemplate.replace(
'Powered by <a href="http://www.cherrypy.org">CherryPy %(version)s</a>\n', '%(version)s')
userpassdict = {'test': 'test'}
checkpassword = cherrypy.lib.auth_basic.checkpassword_dict(userpassdict)
cherrypy.config.update({
'server.socket_host': '0.0.0.0',
'server.socket_port': config['server_port'],
'tools.proxy.on': config['proxy_support'],
'tools.proxy.base': config['proxy_support_base'],
'tools.auth_basic.on': True,
'tools.auth_basic.realm': 'dadns',
'tools.auth_basic.checkpassword': checkpassword,
'tools.response_headers.on': True,
'tools.response_headers.headers': [('Server', 'DirectDNS v' + app_version)],
'environment': config['environment']
})
# cherrypy.log.error_log.propagate = False
# cherrypy.log.access_log.propagate = False
if not lib.common.check_if_super_user_exists(session):
password_str = lib.common.get_random_string(35)
applog.info('Creating superuser account: {}'.format('super'))
applog.info('Password: {}'.format(password_str))
superuser = lib.db.models.Key(key=password_str, name='super', service='*')
session.add(superuser)
session.commit()
else:
applog.info('Superuser account already exists: skipping creation')
cherrypy.quickstart(DaDNS())

0
src/lib/__init__.py Normal file
View File

View File

@@ -0,0 +1,18 @@
import random
import string
import lib.db.models
def check_if_super_user_exists(session):
exists = session.query(session.query(lib.db.models.Key).filter_by(name='super').exists()).scalar()
return exists
def check_if_domain_exists(session):
pass
def get_random_string(length):
letters_and_digits = string.ascii_letters + string.digits
result_str = ''.join(random.choice(letters_and_digits) for i in range(length))
return result_str

15
src/lib/db/__init__.py Normal file
View File

@@ -0,0 +1,15 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import datetime
Base = declarative_base()
def connect():
# Start SQLite engine
engine = create_engine('sqlite:///./config/keys.db', connect_args={'check_same_thread': False})
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
return session

View File

@@ -0,0 +1,28 @@
from lib.db import Base
from sqlalchemy import Column, Integer, String, DateTime
class Key(Base):
__tablename__ = "keys"
id = Column(Integer, primary_key=True)
key = Column(String, unique=True)
name = Column(String)
expires = Column(DateTime)
service = Column(String)
def __repr__(self):
return "<Key(key='%s', name='%s', expires='%s', service='%s')>" % (
self.key, self.name, self.expires, self.service)
class Domain(Base):
__tablename__ = "domains"
id = Column(Integer, primary_key=True)
domain = Column(String, unique=True)
hostname = Column(String)
username = Column(String)
def __repr__(self):
return "<Domain(id='%s', domain='%s', hostname='%s', username='%s')>" % (
self.id, self.domain, self.hostname, self.username
)

6
src/test-file.py Normal file
View File

@@ -0,0 +1,6 @@
import yaml
data = {'ZU7f3NogDxIzhfW5tsv9' : {'name': 'super',
'expires': '11/21/2020, 10:56:24'}}
print(yaml.dump(data))

18
src/test.py Normal file
View File

@@ -0,0 +1,18 @@
import datetime
import lib.common
import lib.db
import lib.db.models
new_expiry_date = datetime.datetime.now() + datetime.timedelta(int(10))
session = lib.db.connect()
if not lib.common.check_if_super_user_exists(session):
password_str = lib.common.get_random_string(20)
print('Creating superuser account: {}'.format('super'))
print('Password: {}'.format(password_str))
super = lib.db.models.Key(key=password_str, name='super', service='*')
session.add(super)
session.commit()
else:
print('Superuser account already exists: skipping creation')