#!rsc by RouterOS # DNSDrone for Mikrotik - DNS Record Updater for FreeIPA # This script: # 1. Consolidates all configuration into a single CONFIG dictionary # 2. Includes a function for making FreeIPA API calls # 3. Gets current IP addresses from DHCP clients # 4. Compares with existing DNS records # 5. Updates records only when they differ # 6. Handles both IPv4 and IPv6 records # 7. Updates the prefix TXT record # # To use this: # 1. Replace the configuration values in the CONFIG dictionary # 2. Save it as a script in your Mikrotik router # 3. Schedule it to run periodically or trigger it manually # # Note: You might need to adjust the HTTP headers and API calls based on your specific FreeIPA version and requirements. Also, ensure your Mikrotik router has proper SSL/TLS # certificates installed for HTTPS communication with the FreeIPA server. # Configuration Variables :local CONFIG { "wanInterface"="VLAN666_Altibox"; "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; # Save Cookie-information in tempoary local file via ugly ssh hack /system ssh "$($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 "\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={ # Retrive information :set resultRAW [/ip dhcp-client get [/ip dhcp-client find interface="$($CONFIG->"wanInterface")"] address]; # Clean information :set resultClean [:pick "$resultRAW" 0 [:find "$resultRAW" "/"]]; # :put "DEBUG: resultClean: $resultClean"; } :if ($ConfigType = "ipv6address") do={ # Retrive information :set resultRAW [/ipv6 dhcp-client get [/ipv6 dhcp-client find interface="$($CONFIG->"wanInterface")"] address]; # Clean information :set resultClean [:pick "$resultRAW" 0 [:find "$resultRAW" ","]]; # :put "DEBUG: resultClean: $resultClean"; } :if ($ConfigType = "ipv6prefix") do={ # Retrive information :set resultRAW [/ipv6 dhcp-client get [/ipv6 dhcp-client find interface="$($CONFIG->"wanInterface")"] prefix]; # Clean information :set resultClean [:pick "$resultRAW" 0 [:find "$resultRAW" ","]]; # :put "DEBUG: resultClean: $resultClean"; } # 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 :set resultRAW [/resolve "$RecordName" server="$($CONFIG->"DNSserver")" type=ipv4] # Clean information :set resultClean "$resultRAW"; # :put "DEBUG: resultClean: $resultClean"; } :if ($ConfigType = "ipv6address" ) do={ # Retrive information :set resultRAW [/resolve "$RecordName" server="$($CONFIG->"DNSserver")" type=ipv6] # Clean information :set resultClean "$resultRAW"; # :put "DEBUG: resultClean: $resultClean"; } :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 output=user http-header-field="accept: application/dns-json" 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; put "DEBUG: IPACookie: $IPACookie"; put "DEBUG: ConfigType: $ConfigType"; put "DEBUG: RecordName: $RecordName"; put "DEBUG: RecordContent: $RecordContent"; :if ($ConfigType = "ipv4address" ) do={ # Construct valid json-data: # :local httpheader "\"Accept: application/json,Content-Type: application/json, Referer: https://$($CONFIG->"IpaServer")/ipa, X-IPA-API-Version: $($CONFIG->"apiVersion"), Cookie: $IPACookie \"\""; :local httpdata "{\"method\":\"dnsrecord_mod\",\"params\":[[\"$($CONFIG->"dnsZone")\",\"$RecordName.\"],{\"arecord\":\"$RecordContent\",\"dnsttl\":$($CONFIG->"recordTTL")}]}"; :local IPACookieData "$cookie"; # :local IPACookieData "ipa_session=MagBearerToken=4ZTumSEI1zR%2fcPkbvlzM2lkT7CR9ojGOsvOzji0MO8ee61xUjZqXX3Ecs9n1FCHl0Vyn1U0EEooAAQk2DZ9GWT1Wt43Zx06sqDjDd5Ku8OJMa0W5SzkQuIQs%2f2hqkFoREevjwefgXurIlSxvDyVofXzJM726ZUAZsGICL9UawnWmo%2f7IVIqPRnNkb5g2NeMJKgThT0xuJ1G4nRY8w0CuIJV%2fJnVk9%2fjK%2bg%2bEjxhE3Lo%3d;path=/ipa;httponly;secure;"; put "DEBUG: (NEW)IPACookie = $IPACookie"; put "DEBUG: IPACookieData = $IPACookieData"; put "DEBUG: http-data=$httpdata"; /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="$httpdata" \ keep-result=no # http-data="{\"method\":\"dnsrecord_mod\",\"params\":[[\"$($CONFIG->"dnsZone")\",\"$RecordName.\"],{\"arecord\":\"$RecordContent\",\"dnsttl\":$($CONFIG->"recordTTL")}]}" \ # Cookie: $IPACookie" \ } # Output :return "$resultClean"; } # Main Runtime #:local IPACookie "drytest"; :local IPACookie [$GetIPACookie $CONFIG]; :put "DEBUG: IPACookie: $IPACookie"; :local CurrentIPv4address [$GetLocalDynamicConfig $CONFIG ipv4address]; :put "DEBUG CurrentIPv4address: $CurrentIPv4address"; :local CurrentIPv6address [$GetLocalDynamicConfig $CONFIG ipv6address]; :put "DEBUG: CurrentIPV6address: $CurrentIPv6address"; :local CurrentIPv6prefix [$GetLocalDynamicConfig $CONFIG ipv6prefix]; :put "DEBUG: CurrentIPv6prefix: $CurrentIPv6prefix"; # "$($CONFIG->"dualStackName").$($CONFIG->"dnsZone")" :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 ]; :put "DEBUG: Dual-stack: $CurrentDualStackRecordName ipv4-resolves as $CurrentDualStackRecordv4"; :local CurrentDualStackRecordv6 [$GetDynamicDNSRecords $CONFIG ipv6address $CurrentDualStackRecordName ]; :put "DEBUG: Dual-stack: $CurrentDualStackRecordName ipv6-resolves as $CurrentDualStackRecordv6"; :local CurrentSingleStackRecordv4 [$GetDynamicDNSRecords $CONFIG ipv4address $CurrentSingleStackIPv4RecordName ]; :put "DEBUG: Single-stack-IPv4: $CurrentSingleStackIPv4RecordName ipv4-resolves as $CurrentSingleStackRecordv4"; :local CurrentSingleStackRecordv6 [$GetDynamicDNSRecords $CONFIG ipv6address $CurrentSingleStackIPv6RecordName ]; :put "DEBUG: Single-stack-IPv6: $CurrentSingleStackIPv6RecordName ipv6-resolves as $CurrentSingleStackRecordv6"; :local CurrentTXTRecordIPv6Prefix [$GetDynamicDNSRecords $CONFIG ipv6prefix $CurrentTXTRecordIPv6PrefixName ]; :put "DEBUG: $CurrentTXTRecordIPv6PrefixName resolves as $CurrentTXTRecordIPv6Prefix"; :local DebugUpdate [$UpdateDynamicDNSRecord $CONFIG $IPACookie ipv4address $CurrentSingleStackIPv4RecordName $CurrentIPv4address ]; :put "DEBUG: $DebugUpdate";