Technitium does have a HTTP API, too. So let’s play with it a bit. In all the examples, I’m piping the output through jq
just for the sake of readability. Here’s how you’d create a record. First, log in:
[root@neth-tech technitium]# curl -s http://localhost:5380/api/login\?user\=admin\&pass\=password | jq
{
"token": "df43d4dc49f5665167ffc6bf311089bb0cf576843f3b8ec27e9df1804733cd20",
"status": "ok"
}
You’ll now use that token in every following request. We’re going to create a record for example.com
, so let’s make sure this server claims to be authoritative for example.com
:
[root@neth-tech technitium]# curl -s "http://localhost:5380/api/listZones?token=df43d4dc49f5665167ffc6bf311089bb0cf576843f3b8ec27e9df1804733cd20" | jq
{
"response": {
"zones": [
(snip)
{
"name": "example.com",
"type": "Primary",
"internal": false,
"disabled": false
},
(snip)
]
},
"status": "ok"
}
Yep, it’s there. So now, create the record:
[root@neth-tech technitium]# curl -s "http://localhost:5380/api/addRecord?token=df43d4dc49f5665167ffc6bf311089bb0cf576843f3b8ec27e9df1804733cd20&domain=_acme-challenge.example.com&type=TXT&value=___validation_token_received_from_the_ca___" | jq
{
"response": {},
"status": "ok"
}
It says OK, so we should be good. Let’s confirm. Note here that I’m querying the Neth server itself, not the Technitium instance directly.
dan@Dan-Hack-Mini ~ dig @192.168.1.218 txt _acme-challenge.example.com
; <<>> DiG 9.10.6 <<>> @192.168.1.218 txt _acme-challenge.example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14089
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;_acme-challenge.example.com. IN TXT
;; ANSWER SECTION:
_acme-challenge.example.com. 3600 IN TXT "___validation_token_received_from_the_ca___"
;; Query time: 5 msec
;; SERVER: 192.168.1.218#53(192.168.1.218)
;; WHEN: Sun Oct 31 06:32:18 EDT 2021
;; MSG SIZE rcvd: 101
OK, so (hypothetically) Let’s Encrypt was able to validate domain control, and we got the certificate. Let’s now delete the record, as it doesn’t serve any further purpose:
[root@neth-tech technitium]# curl -s "http://localhost:5380/api/deleteRecord?token=df43d4dc49f5665167ffc6bf311089bb0cf576843f3b8ec27e9df1804733cd20&domain=_acme-challenge.example.com&type=TXT&value=___validation_token_received_from_the_ca___" | jq
{
"response": {},
"status": "ok"
}
…and test:
dan@Dan-Hack-Mini ~ dig @192.168.1.218 txt _acme-challenge.example.com
; <<>> DiG 9.10.6 <<>> @192.168.1.218 txt _acme-challenge.example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 57993
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
NXDOMAIN, just as it should be. OK, time to log out:
[root@neth-tech technitium]# curl -s "http://localhost:5380/api/logout?token=df43d4dc49f5665167ffc6bf311089bb0cf576843f3b8ec27e9df1804733cd20" | jq
{
"status": "ok"
}
So given this, it’d be pretty simple for Nethserver (or any other application) to make any necessary changes to the DNS system–so, for example, Neth could automatically populate all the AD DNS records, or all the dozens of records that email can use. And certbot or acme.sh, with an appropriate hook script (acme.sh doesn’t include one, and I assume certbot doesn’t either), could automatically generate and remove the records used for domain validation.
A couple of major drawbacks, though:
- AFAIK, there’s only one admin user, with full control. You need that user’s password to use the API–there are no API tokens.
- As a result of the above, there are no limited privileges. Any application (or person) that has the admin password can do absolutely anything with it. Perhaps not the most secure arrangement.
Note also that the record types are case-sensitive. If you try to create a txt
record, for example, rather than a TXT
record, you’ll get an error like this:
{
"status": "error",
"errorMessage": "Requested value 'txt' was not found.",
"stackTrace": " at System.Enum.TryParseByName(RuntimeType enumType, String originalValueString, ReadOnlySpan`1 value, Boolean ignoreCase, Boolean throwOnFailure, UInt64& result)\n at System.Enum.TryParseUInt32Enum(RuntimeType enumType, String originalValueString, ReadOnlySpan`1 value, UInt32 maxInclusive, Boolean ignoreCase, Boolean throwOnFailure, TypeCode type, UInt32& result)\n at System.Enum.TryParse(Type enumType, String value, Boolean ignoreCase, Boolean throwOnFailure, Object& result)\n at System.Enum.Parse(Type enumType, String value)\n at DnsServerCore.WebServiceZonesApi.AddRecord(HttpListenerRequest request) in Z:\\Technitium\\Projects\\DnsServer\\DnsServerCore\\WebServiceZonesApi.cs:line 505\n at DnsServerCore.DnsWebService.ProcessRequestAsync(HttpListenerRequest request, HttpListenerResponse response) in Z:\\Technitium\\Projects\\DnsServer\\DnsServerCore\\DnsWebService.cs:line 519"
}