dnsdrone/dnsdrone.rsc
2025-03-11 20:57:16 +01:00

667 lines
21 KiB
Text
Executable file

#!rsc by RouterOS
# DNSDrone for Mikrotik - DNS Record Updater for FreeIPA
# Configuration Variables
:local CONFIG {
"wanInterface"="VLAN666_WAN_Telenor";
"IpaServer"="ipa.demo1.freeipa.org";
"dnsZone"="demo1.freeipa.org";
"IpaUser"="admin";
"IpaPassword"="Secret123";
"apiVersion"="2.253";
"recordTTL"="300";
# Curlproxy
"curlproxyserver"="pubdns.dmz.skyfritt.net";
"curlproxyuser"="curluser";
"cookiefile"="cookie_output.txt";
# Record Names (without zone)
"dualStackName"="tik-test";
"ipv4OnlyName"="tik-test-v4";
"ipv6OnlyName"="tik-test-v6";
"prefixTxtName"="tik-test-prefix";
# Resolver Config
"DNSserver"="ipa.demo1.freeipa.org";
"DoHserver"="one.one.one.one";
# Script options (true/false)
"Debug"="true"
}
# Functions:
:local GetIPACookie do={
# Import config
:local CONFIG $1;
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "";
:put "* RouterOS-multiline authentication-command (what we want):";
:put "";
:put "/tool fetch url=\"https://$($CONFIG->"IpaServer")/ipa/session/login_password\" \\";
:put " http-method=post \\";
:put " http-header-field=\"Accept: text/plain, \\";
:put " Content-Type: application/x-www-form-urlencoded, \\";
:put " Referer: https://$($CONFIG->"IpaServer")/ipa,\\";
:put " X-IPA-API-Version: $($CONFIG->"apiVersion")\" \\";
:put " http-data=\"user=$($CONFIG->"IpaUser")&password=$($CONFIG->"IpaPassword")\" \\";
:put " output=user-with-headers";
:put "";
:put "* Reverting to ugly curl+ssh workaround instead via the following command:";
:put "";
:put "/system ssh-exec \"$($CONFIG->"curlproxyserver")\" user=\"$($CONFIG->"curlproxyuser")\" \\";
:put " command=\"curl -s -k -i -H 'Accept: text/plain' \\";
:put " -H 'Content-Type: application/x-www-form-urlencoded' \\";
:put " -H 'Referer: https://$($CONFIG->"IpaServer")/ipa' \\";
:put " -H 'X-IPA-API-Version: $($CONFIG->"apiVersion")' \\";
:put " --data-urlencode 'user=$($CONFIG->"IpaUser")' \\";
:put " --data-urlencode 'password=$($CONFIG->"IpaPassword")' \\";
:put " 'https://$($CONFIG->"IpaServer")/ipa/session/login_password'\" \\";
:put " output-to-file=\"$($CONFIG->"cookiefile")\"";
:put "";
}
# Save Cookie-information in tempoary local file via ugly ssh hack
/system ssh-exec "$($CONFIG->"curlproxyserver")" user="$($CONFIG->"curlproxyuser")" \
command="curl -s -k -i -H 'Accept: text/plain' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Referer: https://$($CONFIG->"IpaServer")/ipa' \
-H 'X-IPA-API-Version: $($CONFIG->"apiVersion")' \
--data-urlencode 'user=$($CONFIG->"IpaUser")' \
--data-urlencode 'password=$($CONFIG->"IpaPassword")' \
'https://$($CONFIG->"IpaServer")/ipa/session/login_password'" \
output-to-file="$($CONFIG->"cookiefile")"
# Needs time to save the cookiefile
/delay 2s
# Extract cookie-information from temporary filr
:local cookie;
:local fileContents [/file get "$($CONFIG->"cookiefile")" contents]
:local startIndex [:find $fileContents "Set-Cookie: ipa_session="]
:if ($startIndex != -1) do={
:set startIndex ($startIndex + 12)
:local endIndex [:find $fileContents "\r\n" $startIndex]
:if ($endIndex = -1) do={
:set endIndex [:len $fileContents]
}
:set cookie [:pick $fileContents $startIndex $endIndex]
}
# Cleaning up the cookiefile
/file remove "$($CONFIG->"cookiefile")"
# :put "DEBUG (function: GetIPACookie):;
# :put Ipakjeks: $cookie";
# Output
:return "$cookie";
}
:local GetLocalDynamicConfig do={
# Import config
:local CONFIG $1;
# Valid options: ipv4address, ipv6address, ipv6prefix
:local ConfigType $2;
# result variable
:local resultRAW;
:local resultClean;
:if ($ConfigType = "ipv4address" ) do={
# Retrieve information with error handling
:local dhcpError;
:onerror dhcpError in={
:set resultRAW [/ip dhcp-client get [/ip dhcp-client find interface="$($CONFIG->"wanInterface")"] address];
} do={
:put "* Warning: Failed to get DHCPv4 address for interface $($CONFIG->"wanInterface"): $dhcpError";
# Set default value on error
:set resultRAW "";
}
# Clean information only if resultRAW was set successfully
:if ([:len "$resultRAW"] != 0) do={
:set resultClean [:pick "$resultRAW" 0 [:find "$resultRAW" "/"]];
} else={
:set resultClean "NA";
}
}
:if ($ConfigType = "ipv6address" ) do={
# Retrieve information with error handling
:local dhcpError;
:onerror dhcpError in={
:set resultRAW [/ipv6 dhcp-client get [/ipv6 dhcp-client find interface="$($CONFIG->"wanInterface")"] address];
} do={
:put "* Warning: Failed to get DHCPv6 address for interface $($CONFIG->"wanInterface"): $dhcpError";
# Set default value on error
:set resultRAW "";
}
# Clean information only if resultRAW was set successfully
:if ([:len "$resultRAW"] != 0) do={
:set resultClean [:pick "$resultRAW" 0 [:find "$resultRAW" ","]];
} else={
:set resultClean "NA";
}
}
:if ($ConfigType = "ipv6prefix" ) do={
# Retrieve information with error handling
:local dhcpError;
:onerror dhcpError in={
:set resultRAW [/ipv6 dhcp-client get [/ipv6 dhcp-client find interface="$($CONFIG->"wanInterface")"] prefix];
} do={
:put "* Warning: Failed to get DHCPv6 prefix for interface $($CONFIG->"wanInterface"): $dhcpError";
# Set default value on error
:set resultRAW "";
}
# Clean information only if resultRAW was set successfully
:if ([:len "$resultRAW"] != 0) do={
:set resultClean [:pick "$resultRAW" 0 [:find "$resultRAW" ","]];
} else={
:set resultClean "NA";
}
}
# Output
:return "$resultClean";
}
:local GetDynamicDNSRecords do={
# Import config
:local CONFIG $1;
# Valid options: ipv4address, ipv6address, ipv6prefix
:local ConfigType $2;
# Record name
:local RecordName $3;
# result variable
:local resultRAW;
:local resultClean;
:if ($ConfigType = "ipv4address" ) do={
# Retrive information with error handling
:local resolveError;
:onerror resolveError in={
:set resultRAW [/resolve "$RecordName" server="$($CONFIG->"DNSserver")" type=ipv4];
} do={
:put "* Warning: Failed to resolve IPv4 address for $RecordName: $resolveError";
# You can set a default value or take alternative action
:set resultRAW "";
}
# Clean information (only if resultRAW was set successfully)
:if ([:len "$resultRAW"] != 0) do={
:set resultClean "$resultRAW";
} else={
:set resultClean "NA";
}
}
:if ($ConfigType = "ipv6address" ) do={
# Retrive information with error handling
:local resolveError;
:onerror resolveError in={
:set resultRAW [/resolve "$RecordName" server="$($CONFIG->"DNSserver")" type=ipv6];
} do={
:put "* Warning: Failed to resolve IPv6 address for $RecordName: $resolveError";
# You can set a default value or take alternative action
:set resultRAW "";
}
# Clean information (only if resultRAW was set successfully)
:if ([:len "$resultRAW"] != 0) do={
:set resultClean "$resultRAW";
} else={
:set resultClean "NA";
}
}
:if ($ConfigType = "ipv6prefix") do={
# Fetch prefix TXT-record via DoH server:
:set resultRAW [/tool fetch \
url="https://$($CONFIG->"DoHserver")/dns-query?name=$RecordName&type=TXT" \
http-method=get http-header-field="accept: application/dns-json" \
output=user as-value];
# :put "DEBUG: resultRAW: $resultRAW";
# Get just the data content
:local resultJSON ($resultRAW -> "data");
# :put "DEBUG: resultJSON: $resultJSON";
# Extracting TXT-record-data
:local startIndex [:find $resultJSON "data\":\"" 0];
:local startPos ($startIndex + 7);
:local endIndex [:find $resultJSON "\"}" $startPos];
:local resultTXT [:pick $resultJSON $startPos $endIndex];
# Cleaning the prefix-variable (remove escaped double quotes)
:set resultClean [:pick $resultTXT 2 ([:len $resultTXT] - 2)];
# :put "DEBUG: resultClean: $resultClean";
}
# Output
:return "$resultClean";
}
:local UpdateDynamicDNSRecord do={
# Import config
:local CONFIG $1;
# Import IPACookie
:local IPACookie $2;
# Valid options: ipv4address, ipv6address, ipv6prefix
:local ConfigType $3;
# Record name
:local RecordName $4;
# Record content
:local RecordContent $5;
# result variable
:local resultRAW;
:local resultClean;
:if ($ConfigType = "ipv4address" ) do={
:local IPACookieData "$IPACookie";
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* RouterOS-multiline update-command:";
:put "";
:put "/tool fetch url=\"https://$($CONFIG->"IpaServer")/ipa/session/json\" \\";
:put " http-method=post \\";
:put " http-header-field=\"Accept: application/json, \\";
:put " Content-Type: application/json,\\";
:put " Referer: https://$($CONFIG->"IpaServer")/ipa,\\";
:put " X-IPA-API-Version: $($CONFIG->"apiVersion"),\\";
:put " Cookie: $IPACookieData\" \\";
:put " http-data=\"{\\\"method\\\":\\\"dnsrecord_mod\\\",\\\"params\\\":[[\\\"$($CONFIG->"dnsZone")\\\",\\\"$RecordName.\\\"],{\\\"arecord\\\":\\\"$RecordContent\\\",\\\"dnsttl\\\":$($CONFIG->"recordTTL")}]}\" \\";
:put " keep-result=no";
:put "";
}
/tool fetch url="https://$($CONFIG->"IpaServer")/ipa/session/json" \
http-method=post \
http-header-field="Accept: application/json, \
Content-Type: application/json,\
Referer: https://$($CONFIG->"IpaServer")/ipa,\
X-IPA-API-Version: $($CONFIG->"apiVersion"),\
Cookie: $IPACookieData" \
http-data="{\"method\":\"dnsrecord_mod\",\"params\":[[\"$($CONFIG->"dnsZone")\",\"$RecordName.\"],{\"arecord\":\"$RecordContent\",\"dnsttl\":$($CONFIG->"recordTTL")}]}" \
keep-result=no
}
:if ($ConfigType = "ipv6address" ) do={
:local IPACookieData "$IPACookie";
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* RouterOS-multiline update-command:";
:put "";
:put "/tool fetch url=\"https://$($CONFIG->"IpaServer")/ipa/session/json\" \\";
:put " http-method=post \\";
:put " http-header-field=\"Accept: application/json, \\";
:put " Content-Type: application/json,\\";
:put " Referer: https://$($CONFIG->"IpaServer")/ipa,\\";
:put " X-IPA-API-Version: $($CONFIG->"apiVersion"),\\";
:put " Cookie: $IPACookieData\" \\";
:put " http-data=\"{\\\"method\\\":\\\"dnsrecord_mod\\\",\\\"params\\\":[[\\\"$($CONFIG->"dnsZone")\\\",\\\"$RecordName.\\\"],{\\\"aaaarecord\\\":\\\"$RecordContent\\\",\\\"dnsttl\\\":$($CONFIG->"recordTTL")}]}\" \\";
:put " keep-result=no";
:put "";
}
/tool fetch url="https://$($CONFIG->"IpaServer")/ipa/session/json" \
http-method=post \
http-header-field="Accept: application/json, \
Content-Type: application/json,\
Referer: https://$($CONFIG->"IpaServer")/ipa,\
X-IPA-API-Version: $($CONFIG->"apiVersion"),\
Cookie: $IPACookieData" \
http-data="{\"method\":\"dnsrecord_mod\",\"params\":[[\"$($CONFIG->"dnsZone")\",\"$RecordName.\"],{\"aaaarecord\":\"$RecordContent\",\"dnsttl\":$($CONFIG->"recordTTL")}]}" \
keep-result=no
}
:if ($ConfigType = "ipv6prefix" ) do={
:local IPACookieData "$IPACookie";
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* RouterOS-multiline update-command:";
:put "";
:put "/tool fetch url=\"https://$($CONFIG->"IpaServer")/ipa/session/json\" \\";
:put " http-method=post \\";
:put " http-header-field=\"Accept: application/json, \\";
:put " Content-Type: application/json,\\";
:put " Referer: https://$($CONFIG->"IpaServer")/ipa,\\";
:put " X-IPA-API-Version: $($CONFIG->"apiVersion"),\\";
:put " Cookie: $IPACookieData\" \\";
:put " http-data=\"{\\\"method\\\":\\\"dnsrecord_mod\\\",\\\"params\\\":[[\\\"$($CONFIG->"dnsZone")\\\",\\\"$RecordName.\\\"],{\\\"txtrecord\\\":\\\"\\\\\\\"$RecordContent\\\\\\\"\\\",\\\"dnsttl\\\":$($CONFIG->"recordTTL")}]}\" \\";
:put " keep-result=no";
:put "";
}
/tool fetch url="https://$($CONFIG->"IpaServer")/ipa/session/json" \
http-method=post \
http-header-field="Accept: application/json, \
Content-Type: application/json,\
Referer: https://$($CONFIG->"IpaServer")/ipa,\
X-IPA-API-Version: $($CONFIG->"apiVersion"),\
Cookie: $IPACookieData" \
http-data="{\"method\":\"dnsrecord_mod\",\"params\":[[\"$($CONFIG->"dnsZone")\",\"$RecordName.\"],{\"txtrecord\":\"\\\"$RecordContent\\\"\",\"dnsttl\":$($CONFIG->"recordTTL")}]}" \
keep-result=no
}
:if ( "$($CONFIG->"Debug")" = "true") do={
:put ""
}
# Output
:return "$resultClean";
}
# Main Runtime
:local UpdateDNS;
:local UpdateCurrentDualStackRecordv4;
:local UpdateCurrentDualStackRecordv6;
:local UpdateCurrentSingleStackRecordv4;
:local UpdateCurrentSingleStackRecordv6;
:local UpdateCurrentTXTRecordIPv6Prefix;
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "";
:put "Debug: Collecting information";
:put "#############################";
:put "";
}
:local CurrentIPv4address [$GetLocalDynamicConfig $CONFIG ipv4address];
:local CurrentIPv6address [$GetLocalDynamicConfig $CONFIG ipv6address];
:local CurrentIPv6prefix [$GetLocalDynamicConfig $CONFIG ipv6prefix];
:local CurrentDualStackRecordName "$($CONFIG->"dualStackName").$($CONFIG->"dnsZone")";
:local CurrentSingleStackIPv4RecordName "$($CONFIG->"ipv4OnlyName").$($CONFIG->"dnsZone")";
:local CurrentSingleStackIPv6RecordName "$($CONFIG->"ipv6OnlyName").$($CONFIG->"dnsZone")";
:local CurrentTXTRecordIPv6PrefixName "$($CONFIG->"prefixTxtName").$($CONFIG->"dnsZone")";
:local CurrentDualStackRecordv4 [$GetDynamicDNSRecords $CONFIG ipv4address $CurrentDualStackRecordName ];
:local CurrentDualStackRecordv6 [$GetDynamicDNSRecords $CONFIG ipv6address $CurrentDualStackRecordName ];
:local CurrentSingleStackRecordv4 [$GetDynamicDNSRecords $CONFIG ipv4address $CurrentSingleStackIPv4RecordName ];
:local CurrentSingleStackRecordv6 [$GetDynamicDNSRecords $CONFIG ipv6address $CurrentSingleStackIPv6RecordName ];
:local CurrentTXTRecordIPv6Prefix [$GetDynamicDNSRecords $CONFIG ipv6prefix $CurrentTXTRecordIPv6PrefixName ];
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* CurrentIPv4address via DHCPv4 on '$($CONFIG->"wanInterface")' is '$CurrentIPv4address'";
:put "* CurrentIPv6address via DHCPv6 on '$($CONFIG->"wanInterface")' is '$CurrentIPv6address'";
:put "* CurrentIPv6prefix via DHCPv6 on '$($CONFIG->"wanInterface")' is '$CurrentIPv6prefix'";
:put "";
:put "* '$CurrentDualStackRecordName' has the following IPv4-(A)-record '$CurrentDualStackRecordv4'";
:put "* '$CurrentDualStackRecordName' has the following IPv6-(AAAA)-record '$CurrentDualStackRecordv6';"
:put "* '$CurrentSingleStackIPv4RecordName' has the following IPv4-(A)-record '$CurrentSingleStackRecordv4'";
:put "* '$CurrentSingleStackIPv6RecordName' has the following IPv6-(AAAA)-record '$CurrentSingleStackRecordv6';"
:put "* '$CurrentTXTRecordIPv6PrefixName' has the following prefix-(TXT)-record '$CurrentTXTRecordIPv6Prefix'";
}
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "";
:put "Debug: Evaluating information";
:put "#############################";
:put "";
}
if (("$CurrentDualStackRecordv4" != "$CurrentIPv4address" ) and \
([:typeof [:toip "$CurrentIPv4address"]] = "ip")) do {
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* '$CurrentDualStackRecordName' needs to update the IPv4-(A)-record";
}
:set $UpdateDNS "true";
:set $UpdateCurrentDualStackRecordv4 "true";
}
if (("$CurrentDualStackRecordv6" != "$CurrentIPv6address" ) && \
([:typeof [:toip6 "$CurrentIPv6address"]] = "ip6")) do {
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* '$CurrentDualStackRecordName' needs to update the IPv6-(AAAA)-record";
}
:set $UpdateDNS "true";
:set $UpdateCurrentDualStackRecordv6 "true";
}
if (("$CurrentSingleStackRecordv4" != "$CurrentIPv4address" ) && \
([:typeof [:toip "$CurrentIPv4address"]] = "ip")) do {
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* '$CurrentSingleStackIPv4RecordName' needs to update the IPv4-(A)-record";
}
:set $UpdateDNS "true";
:set $UpdateCurrentSingleStackRecordv4 "true";
}
if (("$CurrentSingleStackRecordv6" != "$CurrentIPv6address" ) && \
([:typeof [:toip6 "$CurrentIPv6address"]] = "ip6")) do {
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* '$CurrentSingleStackIPv6RecordName' needs to update the IPv6-(AAAA)-record";
}
:set $UpdateDNS "true";
:set $UpdateCurrentSingleStackRecordv6 "true";
}
if (("$CurrentTXTRecordIPv6Prefix" != "$CurrentIPv6prefix" ) && \
# The following trick/workaround for the check was recomended by forum-sources:
([:typeof ([[:parse "return $CurrentIPv6prefix"]])] = "ip6-prefix")) do {
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* '$CurrentTXTRecordIPv6PrefixName' needs to update the prefix-(TXT)-record";
}
:set $UpdateDNS "true";
:set $UpdateCurrentTXTRecordIPv6Prefix "true";
}
:put "";
# UPDATE INFORMATION
if ("$UpdateDNS" != "true" ) do {
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "No DNS-updates-nescessary... everything is aye-ok!"
:put "";
}
} else {
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "";
:put "Debug: Updating DNS-information";
:put "###############################";
:put "";
}
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* Attempting to authenticate against '$($CONFIG->"IpaServer")' as user '$($CONFIG->"IpaUser")'";
}
:local IPACookie [$GetIPACookie $CONFIG];
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "";
:put "* The following cookie was recived:";
:put "- '$IPACookie'";
:put "";
}
if ("$UpdateCurrentDualStackRecordv4" = "true" ) do {
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* Attempting to update '$CurrentDualStackRecordName' IPv4-(A)-record with '$CurrentIPv4address' ";
:put ""
}
$UpdateDynamicDNSRecord $CONFIG $IPACookie ipv4address $CurrentDualStackRecordName $CurrentIPv4address;
}
if ("$UpdateCurrentDualStackRecordv6" = "true" ) do {
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* Attempting to update '$CurrentDualStackRecordName' IPv6-(AAAA)-record with '$CurrentIPv6address' ";
:put ""
}
$UpdateDynamicDNSRecord $CONFIG $IPACookie ipv6address $CurrentDualStackRecordName $CurrentIPv6address;
}
if ("$UpdateCurrentSingleStackRecordv4" = "true" ) do {
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* Attempting to update '$CurrentSingleStackIPv4RecordName' IPv4-(A)-record with '$CurrentIPv4address' ";
:put ""
}
$UpdateDynamicDNSRecord $CONFIG $IPACookie ipv4address $CurrentSingleStackIPv4RecordName $CurrentIPv4address;
}
if ("$UpdateCurrentSingleStackRecordv6" = "true" ) do {
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* Attempting to update '$CurrentSingleStackIPv6RecordName' IPv4-(A)-record with '$CurrentIPv6address' ";
:put ""
}
$UpdateDynamicDNSRecord $CONFIG $IPACookie ipv6address $CurrentSingleStackIPv6RecordName $CurrentIPv6address;
}
if ("$UpdateCurrentTXTRecordIPv6Prefix" = "true" ) do {
:if ( "$($CONFIG->"Debug")" = "true") do={
:put "* Attempting to update '$CurrentTXTRecordIPv6PrefixName' prefix-(TXT)-record with '$CurrentIPv6prefix' ";
:put ""
}
$UpdateDynamicDNSRecord $CONFIG $IPACookie ipv6prefix $CurrentTXTRecordIPv6PrefixName $CurrentIPv6prefix;
}
}