I upgraded my home internet connection and as a result I had to give up my ~15y Static IP. Having an ephemeral Dynamic IP means I need to use a dynamic dns service to access my homepc. Although the ISP’s CPE (router) has a few public dynamic dns services, I chose to create a simple solution on my own self-hosted DNS infra.
There are a couple of ways to do that, PowerDNS supports Dynamic Updates but I do not want to open PowerDNS to the internet for this kind of operations. I just want to use cron with a simple curl over https.
PowerDNS WebAPI
to enable and use the Built-in Webserver and HTTP API we need to update our configuration:
/etc/pdns/pdns.conf
api-key=0123456789ABCDEF
api=yes
and restart powerdns auth server.
verify it
ss -tnl 'sport = :8081'
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 10 127.0.0.1:8081 *:*
WebServer API in PHP
Next to build our API in PHP
Basic Auth
By using https means that the transport layer is encrypted so we only need to create a basic auth mechanism.
<?php
if ( !isset($_SERVER["PHP_AUTH_USER"]) ) {
header("WWW-Authenticate: Basic realm='My Realm'");
header("HTTP/1.0 401 Unauthorized");
echo "Restricted area: Only Authorized Personnel Are Allowed to Enter This Area";
exit;
} else {
// code goes here
}
?>
by sending Basic Auth headers, the _SERVER php array variable will contain two extra variables
$_SERVER["PHP_AUTH_USER"]
$_SERVER["PHP_AUTH_PW"]
We do not need to setup an external IDM/LDAP or any other user management system just for this usecase (single user access).
and we can use something like:
<?php
if (($_SERVER["PHP_AUTH_USER"] == "username") && ($_SERVER["PHP_AUTH_PW"] == "very_secret_password")){
// code goes here
}
?>
RRSet Object
We need to create the RRSet Object
here is a simple example
<?php
$comments = array(
);
$record = array(
array(
"disabled" => False,
"content" => $_SERVER["REMOTE_ADDR"]
)
);
$rrsets = array(
array(
"name" => "dyndns.example.org.",
"type" => "A",
"ttl" => 60,
"changetype" => "REPLACE",
"records" => $record,
"comments" => $comments
)
);
$data = array (
"rrsets" => $rrsets
);
?>
by running this data set to json_encode should return something like this
{
"rrsets": [
{
"changetype": "REPLACE",
"comments": [],
"name": "dyndns.example.org.",
"records": [
{
"content": "1.2.3.4",
"disabled": false
}
],
"ttl": 60,
"type": "A"
}
]
}
be sure to verify that records, comments and rrsets are also arrays !
Stream Context
Next thing to create our stream context
$API_TOKEN = "0123456789ABCDEF";
$URL = "http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.org";
$stream_options = array(
"http" => array(
"method" => "PATCH",
"header" => "Content-type: application/json \r\n" .
"X-API-Key: $API_TOKEN",
"content" => json_encode($data),
"timeout" => 3
)
);
$context = stream_context_create($stream_options);
Be aware of " \r\n" .
in header field, this took me more time than it should ! To have multiple header fiels into the http stream, you need (I don’t know why) to carriage return them.
Get Zone details
Before continue, let’s make a small script to verify that we can successfully talk to the PowerDNS HTTP API with php
<?php
$API_TOKEN = "0123456789ABCDEF";
$URL = "http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.org";
$stream_options = array(
"http" => array(
"method" => "GET",
"header" => "Content-type: application/jsonrn".
"X-API-Key: $API_TOKEN"
)
);
$context = stream_context_create($stream_options);
echo file_get_contents($URL, false, $context);
?>
by running this:
php get.php | jq .
we should get the records of our zone in json format.
Cron Entry
you should be able to put the entire codebase together by now, so let’s work on the last component of our self-hosted dynamic dns server, how to update our record via curl
curl -sL https://username:very_secret_password@example.org/dyndns.php
every minute should do the trick
# dyndns
* * * * * curl -sL https://username:very_secret_password@example.org/dyndns.php
That’s it !