Skip to content
This repository has been archived by the owner on Feb 24, 2024. It is now read-only.

Commit

Permalink
Modified keahook.py to update PowerDNS for host reservation changes
Browse files Browse the repository at this point in the history
  • Loading branch information
NIXKnight committed Sep 16, 2023
1 parent f1ef84b commit bbf0aa2
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 17 deletions.
3 changes: 2 additions & 1 deletion docker/kea/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ RUN set -eux; \
socat \
python3 \
python3-pip \
python3-requests \
libpq5 \
libmariadb3 \
libpython3.11 \
Expand Down Expand Up @@ -58,7 +59,7 @@ RUN set -eux; \
--disable-rpath \
--enable-generate-parser \
--without-werror; \
make -j; \
make; \
make install

# Install kea_python https://github.com/invite-networks/kea_python
Expand Down
139 changes: 123 additions & 16 deletions docker/kea/keahook.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
from os import environ
import kea
import json
import requests

powerdns_domain = environ["PDNS_DOMAIN"]
powerdns_api_key = environ["PDNS_API_KEY"]
powerdns_api_host = environ["PDNS_API_HOST"]
powerdns_api_port = environ["PDNS_API_PORT"]
powerdns_api_protocol = environ["PDNS_API_PROTOCOL"]
powerdns_api_url = f"{powerdns_api_protocol}://{powerdns_api_host}:{powerdns_api_port}/api/v1"

class UNSPECIFIED:
pass
Expand Down Expand Up @@ -66,6 +75,91 @@ def wrap_handler(handle, get_response):
return 1
return 0

# Add dns record to PowerDNS via its API
def add_dns_record(host_ip, hostname):
headers = {'X-API-Key': powerdns_api_key}
payload = {
"rrsets": [
{
"name": f"{hostname}.",
"type": "A",
"ttl": 600,
"changetype": "REPLACE",
"records": [
{
"content": host_ip,
"disabled": False
}
]
}
]
}

response = requests.patch(
f"{powerdns_api_url}/servers/localhost/zones/{powerdns_domain}.",
headers=headers,
data=json.dumps(payload)
)

if response.status_code not in [200, 201, 204]:
raise Exception(f"Failed to add DNS record: {response.content}")

# Delete dns record from PowerDNS via its API
def delete_dns_record(hostname):
headers = { 'X-API-Key': powerdns_api_key }

data = {
"rrsets": [
{
"name": f"{hostname}.{powerdns_domain}.",
"type": "A",
"changetype": "DELETE"
}
]
}

response = requests.patch(
f"{powerdns_api_url}/servers/localhost/zones/{powerdns_domain}.",
headers=headers,
data=json.dumps(data)
)

if response.status_code not in [200, 204]:
raise Exception(f"Failed to delete DNS record: {response.content}")


def core_add_reservation(subnet_id, resv, host_mgr):
unsupported_arguments = [ 'identifier-type' ]
# Remove any unsupported keys from the reservation dictionary
for key in unsupported_arguments:
resv.pop(key, None)
host = kea.HostReservationParser4().parse(subnet_id, resv)
host_mgr.add(host)
ip_address = resv.get('ip-address')
hostname = resv.get('hostname')
fqdn = f"{hostname}.{powerdns_domain}"
try:
add_dns_record(ip_address, fqdn)
except Exception as e:
return {'result': 1, 'text': f"Failed to add DNS record: {e}"}
return {'result': 0, 'text': f"Host reservation added: MAC={resv.get('hw-address')}, IP={ip_address}"}

def core_del_reservation(subnet_id, args, host_mgr):
identifier_type = args.get('identifier-type')
# Explicitly get 'identifier' or 'hw-address'
identifier = args.get('identifier') or args.get('hw-address')
if identifier is None:
return {'result': 1, 'text': f"Identifier not provided for identifier type {identifier_type}"}

was_deleted = host_mgr.del4(subnet_id, identifier_type, identifier)
if was_deleted:
hostname = args.get('hostname')
try:
delete_dns_record(hostname)
except Exception as e:
return {'result': 1, 'text': f"Failed to delete DNS record: {e}"}
return {'result': 0, 'text': 'Host deleted.'}
return {'result': 1, 'text': 'Host not deleted (not found).'}

# {"command": "reservation-add",
# "arguments": {"reservation": {"subnet-id": 1,
Expand All @@ -75,11 +169,8 @@ def get_response(args):
resv = get_map_arg(args, 'reservation')
subnet_id = get_int_arg(resv, 'subnet-id')
del resv['subnet-id']
host = kea.HostReservationParser4().parse(subnet_id, resv)
kea.HostMgr.instance().add(host)
return {'result': 0,
'text': 'Host added.'}

host_mgr = kea.HostMgr.instance()
return core_add_reservation(subnet_id, resv, host_mgr)
return wrap_handler(handle, get_response)


Expand Down Expand Up @@ -164,18 +255,33 @@ def get_response(args):
# "identifier": "01:02:03:04:05:06"}}
def reservation_del(handle):
def get_response(args):
host_mgr = kea.HostMgr.instance()
subnet_id = get_int_arg(args, 'subnet-id')
if 'ip-address' in args:
ip_address = get_string_arg(args, 'ip-address')
was_deleted = host_mgr.del_(subnet_id, ip_address)
else:
identifier_type = get_string_arg(args, 'identifier-type')
identifier = get_string_arg(args, 'identifier')
was_deleted = host_mgr.del4(subnet_id, identifier_type, identifier)
if was_deleted:
return {'result': 0, 'text': 'Host deleted.'}
return {'result': 1, 'text': 'Host not deleted (not found).'}
host_mgr = kea.HostMgr.instance()
return core_del_reservation(subnet_id, args, host_mgr)
return wrap_handler(handle, get_response)

def reservation_update(handle):
def get_response(args):
resv = get_map_arg(args, 'reservation')
subnet_id = get_int_arg(resv, 'subnet-id')
del resv['subnet-id']
host_mgr = kea.HostMgr.instance()

hw_address = resv.get('hw-address')
if hw_address is None:
return {'result': 1, 'text': 'hw-address not provided'}

# Ensure that hw_address is a string
if not isinstance(hw_address, str):
return {'result': 1, 'text': 'hw-address must be a string'}

# First delete the old reservation
del_response = core_del_reservation(subnet_id, {'identifier-type': 'hw-address', 'identifier': hw_address}, host_mgr)
if del_response['result'] != 0:
return del_response

# Then add the new reservation
return core_add_reservation(subnet_id, resv, host_mgr)

return wrap_handler(handle, get_response)

Expand All @@ -186,4 +292,5 @@ def load(handle):
handle.registerCommandCallout('reservation-get-all', reservation_get_all)
handle.registerCommandCallout('reservation-get-page', reservation_get_page)
handle.registerCommandCallout('reservation-del', reservation_del)
handle.registerCommandCallout('reservation_update', reservation_update)
return 0

0 comments on commit bbf0aa2

Please sign in to comment.