You've already forked directdnsonly
Initial project commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.db
|
||||
venv/
|
||||
.venv
|
||||
.idea
|
||||
11
Dockerfile
Normal file
11
Dockerfile
Normal 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
16
Dockerfile.new
Normal 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
50
Dockerfile.scratch
Normal 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
14
Pipfile
Normal 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
188
Pipfile.lock
generated
Normal 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
105
app.py
Normal 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
48
docker-compose.yml
Normal 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
0
entrypoint.sh
Normal file
8
requirements.txt
Normal file
8
requirements.txt
Normal 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
0
src/__init__.py
Normal file
8
src/config/app.yml
Normal file
8
src/config/app.yml
Normal 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
|
||||
16
src/config/app.yml.example
Normal file
16
src/config/app.yml.example
Normal 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
2590
src/config/directdns.log
Normal file
File diff suppressed because it is too large
Load Diff
67
src/db.py
Normal file
67
src/db.py
Normal 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
232
src/directdnsonly.py
Normal 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
0
src/lib/__init__.py
Normal file
18
src/lib/common/__init__.py
Normal file
18
src/lib/common/__init__.py
Normal 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
15
src/lib/db/__init__.py
Normal 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
|
||||
28
src/lib/db/models/__init__.py
Normal file
28
src/lib/db/models/__init__.py
Normal 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
6
src/test-file.py
Normal 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
18
src/test.py
Normal 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')
|
||||
Reference in New Issue
Block a user