Skip to content

Instantly share code, notes, and snippets.

@scr34m
Last active June 9, 2023 07:29
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save scr34m/c7af866d9c3e0c4bc5850d651c59b770 to your computer and use it in GitHub Desktop.
Save scr34m/c7af866d9c3e0c4bc5850d651c59b770 to your computer and use it in GitHub Desktop.
Acme.sh DNS API interface for Dotroll
#!/usr/bin/bash
# Dotroll domain api
# - api access values stored per domain, including migration from account stored values
# - zone data is POST-ed to avoid "414 Request-URI Too Large" errors
#
# Initially export values Dotroll_User and Dotroll_Password
# export Dotroll_User='<your.dotroll@user>'; export Dotroll_Password='<dotroll_api_password>'; acme.sh --issue --dns dns_dotroll -d <domain.tld> -d '*.<domain.tld>'
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_dotroll_add() {
fulldomain=$1
txtvalue=$2
_info "Adding TXT record using dotroll API"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
Dotroll_User="${Dotroll_User:-$(_readdomainconf Dotroll_User)}"
Dotroll_Password="${Dotroll_Password:-$(_readdomainconf Dotroll_Password)}"
if [ -z "$Dotroll_User" ] || [ -z "$Dotroll_Password" ]; then
_debug "Try to migrate from account conf"
Acc_Dotroll_User="$(_readaccountconf_mutable Dotroll_User)"
Acc_Dotroll_Password="$(_readaccountconf_mutable Dotroll_Password)"
if [ -n "$Acc_Dotroll_User" ] && [ -n "$Acc_Dotroll_Password" ]; then
Dotroll_User="$Acc_Dotroll_User"
Dotroll_Password="$Acc_Dotroll_Password"
_savedomainconf Dotroll_User "$Acc_Dotroll_User"
_savedomainconf Dotroll_Password "$Acc_Dotroll_Password"
else
Dotroll_User=""
Dotroll_Password=""
_err "You don't specify dotroll user accounts."
_err "Please create you key and try again."
return 1
fi
fi
#save the user and password to the domain conf file.
_savedomainconf Dotroll_User "$Dotroll_User"
_savedomainconf Dotroll_Password "$Dotroll_Password"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting existing records"
_dotroll_rest GET "$_domain/get"
if ! _contains "$response" 'message":"OK"'; then
return 1
fi
if _contains "$response" "$txtvalue"; then
_info "The record is existing, skip"
return 0
fi
# IMPORTANT this does only add new records, the original records are not altered, but are required by the API
records=$(echo "$response" | cut -c 12- | rev | cut -c 40- | rev)
records_list=$(printf "{%s}" "$records")
# unchanged records_list
modify=$(echo "$records_list" | _url_encode)
new=$(echo '[{"name": "'${fulldomain}'.", "type": "TXT", "ttl": 3600, "txtdata": "'${txtvalue}'"}]' | _url_encode)
_dotroll_rest POST "$_domain/modify" "modify=${modify}&new=${new}"
if ! _contains "$response" 'message":"OK"'; then
_err "Add txt record error."
return 1
fi
_info "Added, sleeping 10 seconds"
_sleep 10
#todo: check if the record takes effect
return 0
}
# Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_dotroll_rm() {
fulldomain=$1
txtvalue=$2
_info "Removing TXT record using dotroll API"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
Dotroll_User="${Dotroll_User:-$(_readdomainconf Dotroll_User)}"
Dotroll_Password="${Dotroll_Password:-$(_readdomainconf Dotroll_Password)}"
if [ -z "$Dotroll_User" ] || [ -z "$Dotroll_Password" ]; then
_debug "Try to migrate from account conf"
Acc_Dotroll_User="$(_readaccountconf_mutable Dotroll_User)"
Acc_Dotroll_Password="$(_readaccountconf_mutable Dotroll_Password)"
if [ -n "$Acc_Dotroll_User" ] && [ -n "$Acc_Dotroll_Password" ]; then
Dotroll_User="$Acc_Dotroll_User"
Dotroll_Password="$Acc_Dotroll_Password"
_savedomainconf Dotroll_User "$Acc_Dotroll_User"
_savedomainconf Dotroll_Password "$Acc_Dotroll_Password"
else
Dotroll_User=""
Dotroll_Password=""
_err "You don't specify dotroll user accounts."
_err "Please create you key and try again."
return 1
fi
fi
#save the user and password to the domain conf file.
_savedomainconf Dotroll_User "$Dotroll_User"
_savedomainconf Dotroll_Password "$Dotroll_Password"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting existing records"
_dotroll_rest GET "$_domain/get"
if ! _contains "$response" 'message":"OK"'; then
return 1
fi
if ! _contains "$response" "$txtvalue"; then
_info "The TXT record is missing, skip"
return 0
fi
# IMPORTANT this does only remove a single record by pairs of fulldomain + txtvalue
records=$(echo "$response" | cut -c 12- | rev | cut -c 40- | rev)
records_list=$(printf "{%s}" "$records")
record=$(echo $records_list | _egrep_o ",\"[0-9]+\"\:{*\"name\":\"$fulldomain\.\"[^}]*\"${txtvalue}\"}")
record_cnt=$(echo $records_list | _egrep_o ",\"[0-9]+\"\:{*\"name\":\"$fulldomain\.\"[^}]*\"${txtvalue}\"}" | wc -l)
if [ ! -z "${record}" ]; then
if [ $record_cnt -gt 1 ]; then
while read recordline
do
records_list=$(echo "${records_list}" | sed -e "s|${recordline}||")
_debug _records_list "${records_list}"
done < <(echo "${record}")
else
records_list=$(echo "${records_list}" | sed -e "s|${record}||")
_debug _records_list "${records_list}"
fi
fi
# remove whitespaces
records_list=$(echo "$records_list" | xargs)
# modified records_list
modify=$(echo "$records_list" | _url_encode)
# URL-param 'modify' only!
_dotroll_rest POST "$_domain/modify" "modify=${modify}"
if ! _contains "$response" 'message":"OK"'; then
_err "Remove txt record error."
return 1
fi
_info "Removed, sleeping 10 seconds"
_sleep 10
#todo: check if the record takes effect
return 0
}
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
domain=$1
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
if [ -z "$h" ]; then
#not valid
return 1
fi
_dotroll_rest GET "$h/get"
if ! _contains "$response" 'message":"OK"'; then
_debug "$h not found or error"
else
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$h"
return 0
fi
p="$i"
i=$(_math "$i" + 1)
done
return 1
}
_dotroll_rest() {
m="$1"
ep="$2"
data="$3"
_debug m $m
_debug ep $ep
_dotroll_auth=$(printf "%s:%s" "$Dotroll_User" "$Dotroll_Password" | _base64)
export _H1="Authorization: Basic $_dotroll_auth"
#response=$(_get "https://api.dotroll.com/domains/zone/$ep")
if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "https://api.dotroll.com/domains/zone/$ep" "" "$m")"
else
response=$(_get "https://api.dotroll.com/domains/zone/$ep")
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug response "$response"
return 0
}
@gnanet
Copy link

gnanet commented Jun 10, 2021

Köszi cirmi, legalább nem kell nekem ezt már megirni

@scr34m
Copy link
Author

scr34m commented Jun 10, 2021

Egs! 🍺

@gnanet
Copy link

gnanet commented Sep 10, 2021

Belefutottam autorenew során, hogy a wildcard certhez 2 TXT -t ellenőrizne, ami nem igazán sikerült a megújítás.
Utánaolvasva https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide

Note: Wildcard certificates require two TXT values. When implementing the method make sure that you append the value instead of replacing it

Megnézem, hogy tudok-e erre fixet készíteni, hogy kezelje wildcard esetén a dupla TXT rekordot, ami ráadásul ugyanazon _acme-challenge rekordon kerül a dns-be normál esetben. Ha sikerül akkor majd küldöm a diffet.

@scr34m
Copy link
Author

scr34m commented Sep 10, 2021

Elviekben nem okozna problémát csak a kommunikáció mikéntje adja a nehézséget és ha úgy akarjuk megoldani, hogy független legyen külső tool-októl.

Szóval teszteléshez mindenképen a staging rendszerben végezz próbákat, ellenkező esetben hamar rate limit lesz a vége. Na meg a debug módot kapcsold be.

@gnanet
Copy link

gnanet commented Sep 10, 2021

Én a "brutál sebészi" módszerre gondoltam, és a dotrollapi felé küldött kommunikációt elkülönítve teszteltem volna, amolyan unit-testing formában:
lekövetni milyen paraméterek jönnek át ha wildcard (ott ugye egyszer a domain,és egyszer a *.domain) egyáltalán egyben küldi,vagy külön hívásokra szedve,stb. de igen staging szerót ismerem :D

@gnanet
Copy link

gnanet commented Sep 10, 2021

@scr34m szerintem ez a kis részlet okozza az egyik gondot, mert ha még bennt a wildcard miatt 2xugyanaz a rekord két külön tokennel, akkor a sed nek átadott $record sortöréssel együtt megy. Közben rájöttem, hogy tudatos, hogy sed "s/$record}//" egy } jelet tartalmaz...

  record=$(echo $records_list | _egrep_o ",\"[0-9]+\"\:{*\"name\":\"$fulldomain\.\"[^}]*")
  _debug record $record
  if [ ! -z "$record" ]; then
    records_list=$(echo "$records_list" | sed "s/$record}//")
  fi

ezt kisérletként én átírtam úgy, hogy kezelje ha több soros eredménye van a $record -nak:

  record=$(echo $records_list | _egrep_o ",\"[0-9]+\"\:{*\"name\":\"$fulldomain\.\"[^}]*}")
  record_cnt=$(echo $records_list | _egrep_o ",\"[0-9]+\"\:{*\"name\":\"$fulldomain\.\"[^}]*}" | wc -l)
  _debug record "${record}"
  if [ ! -z "${record}" ]; then
    if [ $record_cnt -gt 1 ]; then
      echo "${record}" | while read recordline
      do
        records_list=$(echo "$records_list" | sed -e "s|${recordline}||")
      done
    else
      records_list=$(echo "$records_list" | sed -e "s|${record}||")
    fi
  fi

@gnanet
Copy link

gnanet commented Sep 10, 2021

és miután igen faintra megírtam a fenti snippetet, rájöttem,hogy ezzel épp hogy mindig csak az utolsó TXT marad meg 🤦
lehet most kéne egy rekesz...hellenergy meg a Na akkor gondolkodjunk

És a leírás megadta a választ:
https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide

... it's highly recommended to implement the rm function too. ...

A dns_dotroll_add() a meglévő rekordokat a lekérdezésekor változatlan tartalommal adja vissza, és hozzáadja az új rekordot
A dns_dotroll_rm() a meglévő rekordokból a neki paraméterben átadott hosztnév és token párost eltávolitja, így egyesével is törölhetőek a wildcard miatt átadott ugyanazon hosztnevek

@scr34m
Copy link
Author

scr34m commented Sep 10, 2021

Igen az RM nem lett itt implementálva és elírás amit fent írsz.

@gnanet
Copy link

gnanet commented Sep 10, 2021

Igen az RM nem lett itt implementálva és elírás amit fent írsz.

Nem elírás, amikor lekéred a $record -ot, az a grep séma nem veszi bele a JSON element lezáró } -ját. Amikor a sed ben a $record} áll akkor az így jelenik meg:

"9999999":{"name":"_acme-challenge.DOMAIN.","type":"TXT","ttl":"3600","txtdata":"DNS-TOKEN"}

Ha kihagyom a } -ot,akkor ez lesz:

"9999999":{"name":"_acme-challenge.DOMAIN.","type":"TXT","ttl":"3600","txtdata":"DNS-TOKEN"

És a JSON szerkezethibás lesz.

Egyenlőre még API lekérdezés és a műveletek eredményének stringként kiírás szintjén prototípus van meg, de bizakodó vagyok. ;)

@gnanet
Copy link

gnanet commented Sep 16, 2021

@scr34m az alábbi változásokra vetnél egy pillantást?

--- dns_dotroll.sh	2021-07-03 05:24:46.432577041 +0200
+++ dns_dotroll.sh	2021-09-16 06:26:47.853243691 +0200
@@ -8,6 +8,10 @@
   fulldomain=$1
   txtvalue=$2
 
+  _info "Adding TXT record using dotroll API"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
   Dotroll_User="${Dotroll_User:-$(_readaccountconf_mutable Dotroll_User)}"
   Dotroll_Password="${Dotroll_Password:-$(_readaccountconf_mutable Dotroll_Password)}"
   if [ -z "$Dotroll_User" ] || [ -z "$Dotroll_Password" ]; then
@@ -43,15 +47,11 @@
     return 0
   fi
 
+  # IMPORTANT this does only add new records, the original records are not altered, but are required by the API
   records=$(echo "$response" | cut -c 12- | rev | cut -c 40- | rev)
   records_list=$(printf "{%s}" "$records")
 
-  record=$(echo $records_list | _egrep_o ",\"[0-9]+\"\:{*\"name\":\"$fulldomain\.\"[^}]*")
-  _debug record $record
-  if [ ! -z "$record" ]; then
-    records_list=$(echo "$records_list" | sed "s/$record}//")
-  fi
-
+  # unchanged records_list
   modify=$(echo "$records_list" | _url_encode)
   new=$(echo '[{"name": "'${fulldomain}'.", "type": "TXT", "ttl": 3600, "txtdata": "'${txtvalue}'"}]' | _url_encode)
 
@@ -67,11 +67,84 @@
   return 0
 }
 
+
+# Usage: rm  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 dns_dotroll_rm() {
   fulldomain=$1
   txtvalue=$2
 
-  #todo
+  _info "Removing TXT record using dotroll API"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  Dotroll_User="${Dotroll_User:-$(_readaccountconf_mutable Dotroll_User)}"
+  Dotroll_Password="${Dotroll_Password:-$(_readaccountconf_mutable Dotroll_Password)}"
+  if [ -z "$Dotroll_User" ] || [ -z "$Dotroll_Password" ]; then
+    Dotroll_User=""
+    Dotroll_Password=""
+    _err "You don't specify dotroll user accounts."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  #save the user and password to the account conf file.
+  _saveaccountconf_mutable Dotroll_User "$Dotroll_User"
+  _saveaccountconf_mutable Dotroll_Password "$Dotroll_Password"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug "Getting existing records"
+
+  _dotroll_rest "$_domain/get"
+  if ! _contains "$response" 'message":"OK"'; then
+    return 1
+  fi
+
+  if ! _contains "$response" "$txtvalue"; then
+    _info "The TXT record is missing, skip"
+    return 0
+  fi
+
+  # IMPORTANT this does only remove a single record by pairs of fulldomain + txtvalue
+  records=$(echo "$response" | cut -c 12- | rev | cut -c 40- | rev)
+  records_list=$(printf "{%s}" "$records")
+
+  record=$(echo $records_list | _egrep_o ",\"[0-9]+\"\:{*\"name\":\"$fulldomain\.\"[^}]*\"${txtvalue}\"}")
+  record_cnt=$(echo $records_list | _egrep_o ",\"[0-9]+\"\:{*\"name\":\"$fulldomain\.\"[^}]*\"${txtvalue}\"}" | wc -l)
+  if [ ! -z "${record}" ]; then
+    if [ $record_cnt -gt 1 ]; then
+     while read recordline
+     do
+        records_list=$(echo "${records_list}" | sed -e "s|${recordline}||")
+        _debug _records_list "${records_list}"
+      done < <(echo "${record}")
+    else
+      records_list=$(echo "${records_list}" | sed -e "s|${record}||")
+      _debug _records_list "${records_list}"
+    fi
+  fi
+
+  # modified records_list
+  modify=$(echo "$records_list" | _url_encode)
+
+  # URL-param 'modify' only!
+  _dotroll_rest "$_domain/modify?modify=${modify}"
+  if ! _contains "$response" 'message":"OK"'; then
+    _err "Remove txt record error."
+    return 1
+  fi
+
+  _info "Removed, sleeping 10 seconds"
+  _sleep 10
+  #todo: check if the record takes effect
+  return 0
 }
 
 #_acme-challenge.www.domain.com

Úgy gondolom ezekkel a módosításokkal működni fog.

@scr34m
Copy link
Author

scr34m commented Sep 16, 2021

Ránézésre parádés 🎺

@gnanet
Copy link

gnanet commented Nov 7, 2021

Pár nap múlva kiderül, hogy sikerült-e a módositás, aztán küldöm egyben a módosított scriptet

@gnanet
Copy link

gnanet commented Nov 10, 2021

Tegnap reggel egy wildcard megújitás cronból sikerrel lefutott, tehát a módosítások működtek. @scr34m emailben mehet a script ?

@scr34m
Copy link
Author

scr34m commented Nov 10, 2021

Jöhet igen, köszi!

@scr34m
Copy link
Author

scr34m commented Jun 6, 2023

@koceka kiegészítése

@gnanet
Copy link

gnanet commented Jun 6, 2023

@scr34m a wildcard megújítás aztán megint nemjött ösze, plusz van hogy ugyanazon telepítésben több különböző dotroll fiókban vannak domainek, és a wildcard igénylésnél horror méretű zónába kell hozzááadni majd letörölni a TXT rekordot. néhány hete ezért átnéztem a scriptet, az eredmény egy POST képes, a dotroll adatokat a domain konfigjába mentő változat lett.

forkolom, aztán lesz egy diff a mostani állapothoz, remélem könnyen mergelhető lesz

@scr34m
Copy link
Author

scr34m commented Jun 6, 2023

Ahh látom én hibásan account conf használtam a domain szintű beállítás helyett, szuper de ez annyira nem is nagy módosítás összeségében.

@scr34m
Copy link
Author

scr34m commented Jun 6, 2023

@gnanet mergelve, köszi

@gnanet
Copy link

gnanet commented Jun 6, 2023

Ahh látom én hibásan account conf használtam a domain szintű beállítás helyett, szuper de ez annyira nem is nagy módosítás összeségében.

nem hibáztál, a legtöbb dns plugin így működik, csak tudod: "olyan hiba/helyzet nem létezik amit a gina nem szopott be"

@gnanet
Copy link

gnanet commented Jun 6, 2023

Ezt ugye benne hagytad?

@koceka kiegészítése

@scr34m
Copy link
Author

scr34m commented Jun 6, 2023

Nem mert azt a commitod kiszedte de most berakom újra :)

@koceka
Copy link

koceka commented Jun 7, 2023

Gyorsan visszaszívtam, mert nem tuti. Ugyan a szkript látszólag megjavul tőle és nem dob hibát, de a rekord nem törlődik.
A scripttől függetlenül nálatok amúgy működik az API rendesen? Nem igazán tudok törölni, próbáltam manuál módszerekkel is (postman-nel) és habár rekordokat hozzáadni gond nélkül tudok vele, de törölni azzal sem.

@gnanet
Copy link

gnanet commented Jun 7, 2023

Azt amit én hasznalok es nemreg mergelte @scr34m azt par honapja már folyamatos wildcard sslekhez hasznalom es nem lattam hogy gondja kett volna

@gnanet
Copy link

gnanet commented Jun 9, 2023

@scr34m @koceka rájöttem miért nem jó nálam a törlés (mert nekem is 404 jön rá):
alaphiba, hogy ha már url-encode-ot használok, ugyanúgy mint a md5 és más hash-eknél is, az echo alapban rátesz egy sortörést, tehát az lesz a gond, hogy a modify data egy 0a ra végződik, amit echo -n -el kikapcsolhatunk:

  # modified records_list
  modify=$(echo -n "$records_list" | _url_encode)

És igazából csodálom, hogy az új hozzáadásánál nem pánikol rá erre a new-linera pedig az url-encode mindkét esetben a végére odatette a new-line-t

@gnanet
Copy link

gnanet commented Jun 9, 2023

Ezzel igazán az a bajom, hogy egy tr -d ' ' | tr -d "\n" egyértelműen a szóközöket és new-lineokat kitakarítja, mindegy mekkora stringről volt szó, az xargs alapműködésénél használt echo mint parancs bizonyos hosszúságig működik így alapértelmezésben.

# remove whitespaces
records_list=$(echo "$records_list" | xargs)

Másik megoldás, hogy a beépített funkciók közül több helyen használt normalizeJson-t lehetne bevetni:

_normalizeJson() {
  sed "s/\" *: *\([\"{\[]\)/\":\1/g" | sed "s/^ *\([^ ]\)/\1/" | tr -d "\r\n"
}

Azután már ha json-t kell POST-olni, akkor egyben leszedte a szóközöket és sortörést, ezt url_encode-olni már gond nélkül lehetne.

@scr34m
Copy link
Author

scr34m commented Jun 9, 2023

Hát szerintem ha most ezt tudod tesztelni a _normalizeJson-val kellene menni mivel azért alakították ki, hogy ilyen eseteket kezeljenek.

@gnanet
Copy link

gnanet commented Jun 9, 2023

Hát szerintem ha most ezt tudod tesztelni a _normalizeJson-val kellene menni mivel azért alakították ki, hogy ilyen eseteket kezeljenek.
@scr34m
Épp május 31-én volt utoljára, de a fork-ban, és a használatban lévő szerveren átírtam _normalizeJson használatára

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment