Compare commits

..

10 Commits

Author SHA1 Message Date
Elizabeth Hunt 702cb85df8
add acme script 2024-04-11 11:17:58 -06:00
Elizabeth Hunt 06926fd786
better arg parsing 2024-04-10 16:17:08 -06:00
Elizabeth Hunt 0c7ac77127
initial setup 2024-04-10 16:13:16 -06:00
Elizabeth 7c62833cb3
add simponic.hate record 2024-04-09 16:43:23 -06:00
Elizabeth 06f1aa3f37
add simponic.hate record 2024-04-09 16:42:36 -06:00
Elizabeth Hunt 2db616aedd
ensure appended dot in cname contents 2024-04-06 16:20:52 -06:00
Elizabeth 42cd90fb44
update endpoitns 2024-04-03 12:16:47 -06:00
Lizzy Hunt 734dcb5d38
why 2024-04-02 14:05:29 -06:00
Lizzy Hunt a0805ff306
add endpoint record 2024-04-02 13:18:54 -06:00
Lizzy Hunt 48237d1b0f
fix :3 2024-04-02 13:01:40 -06:00
10 changed files with 332 additions and 115 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
*.secret *.secret
**/__pycache__
certbot

41
README.md Normal file
View File

@ -0,0 +1,41 @@
# hatecomputers.club dns updater & certbot plugin
this is a simple wrapper over hatecomputers.club's dns api
## dns creation steps
1. obtain an api key at [hatecomputers.club](https://hatecomputers.club)
2. put it in `apikey.secret`
3. modify `records.json` to your liking
4. `./main.py --create --records-file=records.json`
## certbot plugin
follow the above to generate an api key.
if you use the split-zone dns provided by hatecomputers.club and run your own certificate
authority, you can try something like:
```bash
REQUESTS_CA_BUNDLE=~/armin/roots.pem certbot certonly \
--manual --manual-auth-hook ./plugin.sh \
--preferred-challenges dns \
-d *.internal.simponic.xyz \
--config-dir ./certbot \
--work-dir ./certbot \
--logs-dir ./certbot \
--server https://ca.internal.simponic.xyz/acme/ACME/directory \
--email simponic@hatecomputers.club \
--agree-tos \
--no-eff-email
```
otherwise:
```bash
sudo certbot certonly \
--manual --manual-auth-hook ./plugin.sh \
--preferred-challenges dns \
-d *.simponic.hatecomputers.club \
--email simponic@hatecomputers.club \
--agree-tos \
--no-eff-email
```

59
args.py Normal file
View File

@ -0,0 +1,59 @@
import argparse
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"--endpoint", default="https://hatecomputers.club", help="API endpoint"
)
parser.add_argument(
"--api-key-file",
default="apikey.secret",
help="path to file containing the API key",
)
parser.add_argument(
"--log-level",
default="INFO",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
)
parser.add_argument(
"--public-suffixes",
default="hatecomputers.club",
help="comma separated list of public suffixes",
)
parser.add_argument(
"--dns-propogate-time",
default=20,
type=int,
help="time to sleep to allow DNS to propogate",
)
parser.add_argument(
"--certbot", action="store_true", default=False, help="enable certbot mode"
)
parser.add_argument(
"--certbot-domain", required=False, help="splat/domain to validate with certbot"
)
parser.add_argument(
"--certbot-validation", required=False, help="validation token for certbot"
)
parser.add_argument(
"--create",
action="store_true",
default=False,
help="upload records file to API to sync",
)
parser.add_argument("--records-file", default="records.json", help="records file")
args = parser.parse_args()
if (args.certbot) and (not args.certbot_domain):
parser.error("--certbot-domain is required when --certbot is used")
if (args.certbot) and (not args.certbot_validation):
parser.error("--certbot-validation is required when --certbot is used")
if args.certbot:
args.public_suffixes = args.public_suffixes.split(",")
return args

57
main.py Executable file
View File

@ -0,0 +1,57 @@
#!/usr/bin/env python3
import os
import json
import logging
import time
from updater.adapter import HatecomputersDNSAdapter
from updater.utils import record_transformer
from args import get_args
def certbot_mode(args, dns_api_adapter, record_transformer):
domain = args.certbot_domain
if domain.startswith("*."):
domain = domain[2:]
logging.info(f"processing domain {domain}")
record = {
"ttl": 60,
"name": "_acme-challenge." + domain,
"type": "TXT",
"content": args.certbot_validation,
}
record = record_transformer(record)
logging.info(f"creating record {record}")
dns_api_adapter.post_record(record)
logging.info(
f"eeping out for {args.dns_propogate_time}s, to allow DNS propogation. look at this cute little guy 🐢 until then!!"
)
time.sleep(args.dns_propogate_time)
logging.info(f"updating record for {domain} with {args.certbot_validation}")
if __name__ == "__main__":
args = get_args()
logging.basicConfig()
logging.root.setLevel(args.log_level)
api_key = open(args.api_key_file, "r").read().strip()
dns_api_adapter = HatecomputersDNSAdapter(args.endpoint, api_key)
if args.create:
records_file = open(args.records_file, "r")
dns_records = json.load(records_file)
dns_api_adapter.post_records(dns_records)
if args.certbot:
certbot_mode(
args,
dns_api_adapter,
record_transformer(args.public_suffixes),
)
logging.info("done")

14
plugin.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
unset REQUESTS_CA_BUNDLE
API_KEY_FILE=/Users/lizzy/git/simponic/dns-updater/apikey.secret
ENDPOINT=https://hatecomputers.club
PUBLIC_SUFFIXES=.hatecomputers.club
./main.py --certbot \
--public-suffixes=$PUBLIC_SUFFIXES \
--certbot-domain=$CERTBOT_DOMAIN \
--certbot-validation=$CERTBOT_VALIDATION \
--endpoint=$ENDPOINT \
--api-key-file=$API_KEY_FILE

View File

@ -1,79 +1,115 @@
[ [
{ {
"type": "A", "type": "A",
"name": "johan.internal.simponic.xyz.", "name": "johan.internal.simponic.xyz.",
"content": "100.64.0.5", "content": "100.64.0.5",
"ttl": "43200", "ttl": "43200",
"internal": "on" "internal": "on"
}, },
{ {
"type": "A", "type": "A",
"name": "europa.internal.simponic.xyz.", "name": "europa.internal.simponic.xyz.",
"content": "100.64.0.8", "content": "100.64.0.8",
"ttl": "43200", "ttl": "43200",
"internal": "on" "internal": "on"
}, },
{ {
"type": "CNAME", "type": "CNAME",
"name": "vaultwarden.internal.simponic.xyz.", "name": "vaultwarden.internal.simponic.xyz.",
"content": "johan.internal.simponic.xyz", "content": "johan.internal.simponic.xyz.",
"ttl": "43200", "ttl": "43200",
"internal": "on" "internal": "on"
}, },
{ {
"type": "CNAME", "type": "CNAME",
"name": "lldap.internal.simponic.xyz.", "name": "lldap.internal.simponic.xyz.",
"content": "johan.internal.simponic.xyz.", "content": "johan.internal.simponic.xyz.",
"ttl": "43200", "ttl": "43200",
"internal": "on" "internal": "on"
}, },
{ {
"type": "CNAME", "type": "CNAME",
"name": "ca.internal.simponic.xyz.", "name": "ca.internal.simponic.xyz.",
"content": "johan.internal.simponic.xyz.", "content": "johan.internal.simponic.xyz.",
"ttl": "43200", "ttl": "43200",
"internal": "on" "internal": "on"
}, },
{ {
"type": "CNAME", "type": "CNAME",
"name": "pihole.internal.simponic.xyz.", "name": "pihole.internal.simponic.xyz.",
"content": "johan.internal.simponic.xyz.", "content": "johan.internal.simponic.xyz.",
"ttl": "43200", "ttl": "43200",
"internal": "on" "internal": "on"
}, },
{ {
"type": "CNAME", "type": "CNAME",
"name": "owncloud.internal.simponic.xyz.", "name": "owncloud.internal.simponic.xyz.",
"content": "europa.internal.simponic.xyz.", "content": "europa.internal.simponic.xyz.",
"ttl": "43200", "ttl": "43200",
"internal": "on" "internal": "on"
}, },
{ {
"type": "CNAME", "type": "CNAME",
"name": "jellyfin.internal.simponic.xyz.", "name": "jellyfin.internal.simponic.xyz.",
"content": "europa.internal.simponic.xyz.", "content": "europa.internal.simponic.xyz.",
"ttl": "43200", "ttl": "43200",
"internal": "on" "internal": "on"
}, },
{ {
"type": "CNAME", "type": "CNAME",
"name": "drone.internal.simponic.xyz.", "name": "drone.internal.simponic.xyz.",
"content": "europa.internal.simponic.xyz.", "content": "europa.internal.simponic.xyz.",
"ttl": "43200", "ttl": "43200",
"internal": "on" "internal": "on"
}, },
{ {
"type": "CNAME", "type": "CNAME",
"name": "scurvy.internal.simponic.xyz.", "name": "scurvy.internal.simponic.xyz.",
"content": "europa.internal.simponic.xyz.", "content": "europa.internal.simponic.xyz.",
"ttl": "43200", "ttl": "43200",
"internal": "on" "internal": "on"
}, },
{ {
"type": "CNAME", "type": "CNAME",
"name": "roundcube.internal.simponic.xyz.", "name": "roundcube.internal.simponic.xyz.",
"content": "europa.internal.simponic.xyz.", "content": "europa.internal.simponic.xyz.",
"ttl": "43200", "ttl": "43200",
"internal": "on" "internal": "on"
} },
{
"type": "CNAME",
"name": "simponic.endpoints",
"content": "levi.simponic.xyz.",
"ttl": "43200",
"internal": "off"
},
{
"type": "CNAME",
"name": "simponic",
"content": "simponic.xyz.",
"ttl": "43200",
"internal": "off"
},
{
"type": "A",
"name": "armin.internal.simponic.xyz",
"content": "100.64.0.6",
"ttl": "43200",
"internal": "on"
},
{
"type": "CNAME",
"name": "dev.armin.internal.simponic.xyz",
"content": "armin.internal.simponic.xyz",
"ttl": "43200",
"internal": "on"
},
{
"type": "CNAME",
"name": "traefik.armin.internal.simponic.xyz.",
"content": "armin.internal.simponic.xyz.",
"ttl": "43200",
"internal": "on"
}
] ]

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
requests==2.31.0

View File

@ -1,38 +0,0 @@
import json
import requests
import time
import logging
RECORDS_FILE = "records.json"
ENDPOINT = "https://hatecomputers.club"
API_KEY = open('apikey.secret', 'r').read().strip()
class HatecomputersDNSAdapter:
def __init__(self, endpoint, api_key):
self.endpoint = endpoint
self.session = requests.Session()
self.headers = {'Authorization': 'Bearer ' + api_key}
self.session = requests.Session()
def post_record(self, record):
endpoint = self.endpoint + "/dns"
logging.info("adding", record, "at", endpoint)
self.session.post(endpoint, headers=self.headers, data=record)
def post_records(self, dns_entries, sleep_time=300):
for record in dns_entries:
self.post_record(record)
logging.info("sleeping", sleep_time)
time.sleep(sleep_time)
if __name__ == "__main__":
logging.basicConfig()
logging.root.setLevel(logging.NOTSET)
records_file = open(RECORDS_FILE, 'r')
dns_records = json.load(records_file)
adapter = HatecomputersDNSAdapter(ENDPOINT, API_KEY)
adapter.post_records(dns_records)

24
updater/adapter.py Normal file
View File

@ -0,0 +1,24 @@
import requests
import time
import logging
class HatecomputersDNSAdapter:
def __init__(self, endpoint, api_key, logger=None):
self.endpoint = endpoint
self.session = requests.Session()
self.headers = {"Authorization": "Bearer " + api_key}
self.logger = logger or logging.getLogger(__name__)
def post_record(self, record):
endpoint = self.endpoint + "/dns"
self.logger.info(f"adding {record} to {endpoint}")
self.session.post(endpoint, headers=self.headers, data=record)
def post_records(self, dns_entries, eepy_time=0.25):
for record in dns_entries:
self.post_record(record)
self.logger.info(f"eeping out for {eepy_time}s")
time.sleep(eepy_time)

21
updater/utils.py Normal file
View File

@ -0,0 +1,21 @@
import logging
def record_transformer(public_suffixes):
def transform(record):
name = record["name"]
suffixes = [suffix for suffix in public_suffixes if name.endswith(suffix)]
suffix = suffixes[0] if suffixes else None
if suffix:
logging.debug(f"stripping {suffix} from {name} as it is a public suffix")
record["name"] = name[: -len(suffix)]
record["internal"] = "off"
return record
logging.debug(f"keeping {name} as it is not a public suffix")
record["internal"] = "on"
return record
return transform