From 470e42098e622099f92194adcd7669d10d04efda Mon Sep 17 00:00:00 2001 From: Shane Bishop Date: Mon, 5 Sep 2022 15:34:53 -0600 Subject: [PATCH 1/4] Create dns_bunny.sh --- dnsapi/dns_bunny.sh | 247 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 dnsapi/dns_bunny.sh diff --git a/dnsapi/dns_bunny.sh b/dnsapi/dns_bunny.sh new file mode 100644 index 00000000..cd41e03d --- /dev/null +++ b/dnsapi/dns_bunny.sh @@ -0,0 +1,247 @@ +#!/usr/bin/env sh + +## Will be called by acme.sh to add the txt record to your api system. +## returns 0 means success, otherwise error. + +## Author: nosilver4u +## GitHub: https://github.com/nosilver4u/acme.sh + +## +## Environment Variables Required: +## +## BUNNY_API_KEY="75310dc4-ca77-9ac3-9a19-f6355db573b49ce92ae1-2655-3ebd-61ac-3a3ae34834cc" +## + +##################### Public functions ##################### + +## Create the text record for validation. +## Usage: fulldomain txtvalue +## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs" +dns_bunny_add() { + fulldomain="$(echo "$1" | _lower_case)" + txtvalue=$2 + + BUNNY_API_KEY="${BUNNY_API_KEY:-$(_readaccountconf_mutable BUNNY_API_KEY)}" + # Check if API Key Exists + if [ -z "$BUNNY_API_KEY" ]; then + BUNNY_API_KEY="" + _err "You did not specify Bunny.net API key." + _err "Please export BUNNY_API_KEY and try again." + return 1 + fi + + _info "Using Bunny.net dns validation - add record" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + ## save the env vars (key and domain split location) for later automated use + _saveaccountconf_mutable BUNNY_API_KEY "$BUNNY_API_KEY" + + ## split the domain for Bunny API + if ! _get_base_domain "$fulldomain"; then + _err "domain not found in your account for addition" + return 1 + fi + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + _debug _domain_id "$_domain_id" + + ## Set the header with our post type and key auth key + export _H1="Accept: application/json" + export _H2="AccessKey: $BUNNY_API_KEY" + export _H3="Content-Type: application/json" + PURL="https://api.bunny.net/dnszone/$_domain_id/records" + PBODY='{"Id":'$_domain_id',"Type":3,"Name":"'$_sub_domain'","Value":"'$txtvalue'","ttl":120}' + + _debug PURL "$PURL" + _debug PBODY "$PBODY" + + ## the create request - post + ## args: BODY, URL, [need64, httpmethod] + response="$(_post "$PBODY" "$PURL" "" "PUT")" + + ## check response + if [ "$?" != "0" ]; then + _err "error in response: $response" + return 1 + fi + _debug2 response "$response" + + ## finished correctly + return 0 +} + +## Remove the txt record after validation. +## Usage: fulldomain txtvalue +## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs" +dns_bunny_rm() { + fulldomain="$(echo "$1" | _lower_case)" + txtvalue=$2 + + BUNNY_API_KEY="${BUNNY_API_KEY:-$(_readaccountconf_mutable BUNNY_API_KEY)}" + # Check if API Key Exists + if [ -z "$BUNNY_API_KEY" ]; then + BUNNY_API_KEY="" + _err "You did not specify Bunny.net API key." + _err "Please export BUNNY_API_KEY and try again." + return 1 + fi + + _info "Using Bunny.net dns validation - remove record" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + ## split the domain for Bunny API + if ! _get_base_domain "$fulldomain"; then + _err "domain not found in your account for removal" + return 1 + fi + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + _debug _domain_id "$_domain_id" + + ## Set the header with our post type and key auth key + export _H1="Accept: application/json" + export _H2="AccessKey: $BUNNY_API_KEY" + ## get URL for the list of DNS records + GURL="https://api.bunny.net/dnszone/$_domain_id" + + ## 1) get the URL + ## the create request - get + ## args: URL, [onlyheader, timeout] + domain_list="$(_get "$GURL")" + + ## check response + if [ "$?" != "0" ]; then + _err "error in domain_list response: $domain_list" + return 1 + fi + _debug2 domain_list "$domain_list" + + ## 2) find records + ## check for what we are looking for: "type":"A","name":"$_sub_domain" + record="$(echo "$domain_list" | _egrep_o "\"Id\"\s*\:\s*\"*[0-9]+\"*,\s*\"Type\"[^}]*\"Value\"\s*\:\s*\"$txtvalue\"[^}]*\"Name\"\s*\:\s*\"$_sub_domain\"")" + + if [ -n "$record" ]; then + + ## we found records + rec_ids="$(echo "$record" | _egrep_o "Id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")" + _debug rec_ids "$rec_ids" + if [ -n "$rec_ids" ]; then + echo "$rec_ids" | while IFS= read -r rec_id; do + ## delete the record + ## delete URL for removing the one we don't want + DURL="https://api.bunny.net/dnszone/$_domain_id/records/$rec_id" + + ## the create request - delete + ## args: BODY, URL, [need64, httpmethod] + response="$(_post "" "$DURL" "" "DELETE")" + + ## check response (sort of) + if [ "$?" != "0" ]; then + _err "error in remove response: $response" + return 1 + fi + _debug2 response "$response" + + done + fi + fi + + ## finished correctly + return 0 +} + +##################### Private functions below ##################### + +## Split the domain provided into the "base domain" and the "start prefix". +## This function searches for the longest subdomain in your account +## for the full domain given and splits it into the base domain (zone) +## and the prefix/record to be added/removed +## USAGE: fulldomain +## EG: "_acme-challenge.two.three.four.domain.com" +## returns +## _sub_domain="_acme-challenge.two" +## _domain="three.four.domain.com" *IF* zone "three.four.domain.com" exists +## _domain_id=234 +## if only "domain.com" exists it will return +## _sub_domain="_acme-challenge.two.three.four" +## _domain="domain.com" +## _domain_id=234 +_get_base_domain() { + # args + fulldomain="$(echo "$1" | _lower_case)" + _debug fulldomain "$fulldomain" + + # domain max legal length = 253 + MAX_DOM=255 + page=1 + + ## get a list of domains for the account to check thru + ## Set the headers + export _H1="Accept: application/json" + export _H2="AccessKey: $BUNNY_API_KEY" + _debug BUNNY_API_KEY "$BUNNY_API_KEY" + ## get URL for the list of domains + ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}} + DOMURL="https://api.bunny.net/dnszone" + + ## while we dont have a matching domain we keep going + while [ -z "$found" ]; do + ## get the domain list (current page) + domain_list="$(_get "$DOMURL")" + + ## check response + if [ "$?" != "0" ]; then + _err "error in domain_list response: $domain_list" + return 1 + fi + _debug2 domain_list "$domain_list" + + ## for each shortening of our $fulldomain, check if it exists in the $domain_list + ## can never start on 1 (aka whole $fulldomain) as $fulldomain starts with "_acme-challenge" + i=2 + while [ $i -gt 0 ]; do + ## get next longest domain + _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM") + ## check we got something back from our cut (or are we at the end) + if [ -z "$_domain" ]; then + break + fi + ## we got part of a domain back - grep it out + found="$(echo "$domain_list" | _egrep_o "\"Id\"\s*:\s*\"*[0-9]+\"*,\s*\"Domain\"\s*\:\s*\"$_domain\"")" + ## check if it exists + if [ -n "$found" ]; then + ## exists - exit loop returning the parts + sub_point=$(_math $i - 1) + _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point") + _domain_id="$(echo "$found" | _egrep_o "Id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")" + _debug _domain_id "$_domain_id" + _debug _domain "$_domain" + _debug _sub_domain "$_sub_domain" + return 0 + fi + ## increment cut point $i + i=$(_math $i + 1) + done + + if [ -z "$found" ]; then + page=$(_math $page + 1) + nextpage="https://api.bunny.net/dnszone?page=$page" + ## find the next page if we dont have a match + hasnextpage="$(echo "$domain_list" | _egrep_o "\"HasMoreItems\"\s*:\s*true")" + if [ -z "$hasnextpage" ]; then + _err "no record and no nextpage in Bunny.net domain search" + return 1 + fi + _debug2 nextpage "$nextpage" + DOMURL="$nextpage" + fi + + done + + ## we went through the entire domain zone list and didn't find one that matched + ## doesnt look like we can add in the record + _err "domain not found in Bunny.net account, but we should never get here" + return 1 +} From df32e6127bba97ef1215dca25886c6179e8d5959 Mon Sep 17 00:00:00 2001 From: Shane Bishop Date: Mon, 5 Sep 2022 16:04:07 -0600 Subject: [PATCH 2/4] Update comments in header --- dnsapi/dns_bunny.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_bunny.sh b/dnsapi/dns_bunny.sh index cd41e03d..bc39fc38 100644 --- a/dnsapi/dns_bunny.sh +++ b/dnsapi/dns_bunny.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -## Will be called by acme.sh to add the txt record to your api system. +## This will be called by acme.sh to add the TXT record to the Bunny.net API. ## returns 0 means success, otherwise error. ## Author: nosilver4u From 22f7deed31085473392307acf271c9ab36e6d789 Mon Sep 17 00:00:00 2001 From: Shane Bishop Date: Mon, 5 Sep 2022 16:23:42 -0600 Subject: [PATCH 3/4] update get_domain failure comments --- dnsapi/dns_bunny.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_bunny.sh b/dnsapi/dns_bunny.sh index bc39fc38..2219022b 100644 --- a/dnsapi/dns_bunny.sh +++ b/dnsapi/dns_bunny.sh @@ -240,8 +240,8 @@ _get_base_domain() { done - ## we went through the entire domain zone list and didn't find one that matched - ## doesnt look like we can add in the record + ## We went through the entire domain zone list and didn't find one that matched! + ## That's not right, throw an error... _err "domain not found in Bunny.net account, but we should never get here" return 1 } From 04d42f5cb204bc7c4792dc1bc9943ba48c6b87b8 Mon Sep 17 00:00:00 2001 From: Shane Bishop Date: Mon, 5 Sep 2022 16:37:58 -0600 Subject: [PATCH 4/4] reset found domain response after completion Hopefully this prevents it leaking between tests/requests. --- dnsapi/dns_bunny.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dnsapi/dns_bunny.sh b/dnsapi/dns_bunny.sh index 2219022b..f8cb085a 100644 --- a/dnsapi/dns_bunny.sh +++ b/dnsapi/dns_bunny.sh @@ -194,6 +194,7 @@ _get_base_domain() { ## check response if [ "$?" != "0" ]; then _err "error in domain_list response: $domain_list" + found="" return 1 fi _debug2 domain_list "$domain_list" @@ -219,6 +220,7 @@ _get_base_domain() { _debug _domain_id "$_domain_id" _debug _domain "$_domain" _debug _sub_domain "$_sub_domain" + found="" return 0 fi ## increment cut point $i @@ -232,6 +234,7 @@ _get_base_domain() { hasnextpage="$(echo "$domain_list" | _egrep_o "\"HasMoreItems\"\s*:\s*true")" if [ -z "$hasnextpage" ]; then _err "no record and no nextpage in Bunny.net domain search" + found="" return 1 fi _debug2 nextpage "$nextpage" @@ -243,5 +246,6 @@ _get_base_domain() { ## We went through the entire domain zone list and didn't find one that matched! ## That's not right, throw an error... _err "domain not found in Bunny.net account, but we should never get here" + found="" return 1 }