Compare commits
10 Commits
812eaa893d
...
702cb85df8
Author | SHA1 | Date |
---|---|---|
Elizabeth Hunt | 702cb85df8 | |
Elizabeth Hunt | 06926fd786 | |
Elizabeth Hunt | 0c7ac77127 | |
Elizabeth | 7c62833cb3 | |
Elizabeth | 06f1aa3f37 | |
Elizabeth Hunt | 2db616aedd | |
Elizabeth | 42cd90fb44 | |
Lizzy Hunt | 734dcb5d38 | |
Lizzy Hunt | a0805ff306 | |
Lizzy Hunt | 48237d1b0f |
|
@ -1 +1,3 @@
|
||||||
*.secret
|
*.secret
|
||||||
|
**/__pycache__
|
||||||
|
certbot
|
|
@ -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
|
||||||
|
```
|
|
@ -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
|
|
@ -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")
|
|
@ -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
|
190
records.json
190
records.json
|
@ -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"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
requests==2.31.0
|
38
script.py
38
script.py
|
@ -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)
|
|
|
@ -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)
|
|
@ -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
|
Loading…
Reference in New Issue