From 02d64cf0ccaf5fbbebf71070a6d237369d02875b Mon Sep 17 00:00:00 2001 From: Frank Laszlo Date: Thu, 23 Aug 2018 12:25:57 -0400 Subject: [PATCH] add Nexcess/Thermo/FH DNS providers --- dnsapi/README.md | 57 +++++++++++++- dnsapi/dns_fh.sh | 179 ++++++++++++++++++++++++++++++++++++++++++ dnsapi/dns_nexcess.sh | 179 ++++++++++++++++++++++++++++++++++++++++++ dnsapi/dns_thermo.sh | 179 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 591 insertions(+), 3 deletions(-) create mode 100644 dnsapi/dns_fh.sh create mode 100644 dnsapi/dns_nexcess.sh create mode 100644 dnsapi/dns_thermo.sh diff --git a/dnsapi/README.md b/dnsapi/README.md index 1f394f92..dff54fa9 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -1,6 +1,6 @@ # How to use DNS API -If your dns provider doesn't provide api access, you can use our dns alias mode: +If your dns provider doesn't provide api access, you can use our dns alias mode: https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode @@ -849,7 +849,7 @@ acme.sh --issue --dns dns_loopia -d example.com -d *.example.com The username and password will be saved in `~/.acme.sh/account.conf` and will be reused when needed. ## 45. Use ACME DNS API -ACME DNS is a limited DNS server with RESTful HTTP API to handle ACME DNS challenges easily and securely. +ACME DNS is a limited DNS server with RESTful HTTP API to handle ACME DNS challenges easily and securely. https://github.com/joohoi/acme-dns ``` @@ -897,6 +897,57 @@ acme.sh --issue --dns dns_euserv -d example.com -d *.example.com --insecure The `EUSERV_Username` and `EUSERV_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. Please report any issues to https://github.com/initit/acme.sh or to +## 48. Use Nexcess.net API + +First, you'll need to login to the [Nexcess.net Client Portal](https://portal.nexcess.net) and [generate a new API token](https://portal.nexcess.net/api-token). + +Once you have a token, set it in your systems environment: + +``` +export NEXCESS_API_TOKEN="YOUR_TOKEN_HERE" +``` + +Finally, we'll issue the certificate: (Nexcess DNS publishes at max every 15 minutes, we recommend setting a 1200 second `--dnssleep`) + +``` +acme.sh --issue --dns dns_nexcess -d example.com --dnssleep 1200 +``` + +The `NEXCESS_API_TOKEN will be saved in `~/.acme.sh/account.conf` and will be reused when needed. +## 49. Use Thermo.io API + +First, you'll need to login to the [Thermo.io Client Portal](https://core.thermo.io) and [generate a new API token](https://core.thermo.io/api-token). + +Once you have a token, set it in your systems environment: + +``` +export THERMO_API_TOKEN="YOUR_TOKEN_HERE" +``` + +Finally, we'll issue the certificate: (Thermo DNS publishes at max every 15 minutes, we recommend setting a 1200 second `--dnssleep`) + +``` +acme.sh --issue --dns dns_thermo -d example.com --dnssleep 1200 +``` + +The `THERMO_API_TOKEN will be saved in `~/.acme.sh/account.conf` and will be reused when needed. +## 50. Use Futurehosting API + +First, you'll need to login to the [Futurehosting Client Portal](https://my.futurehosting.com) and [generate a new API token](https://my.futurehosting.com/api-token). + +Once you have a token, set it in your systems environment: + +``` +export FH_API_TOKEN="YOUR_TOKEN_HERE" +``` + +Finally, we'll issue the certificate: (Futurehosting DNS publishes at max every 15 minutes, we recommend setting a 1200 second `--dnssleep`) + +``` +acme.sh --issue --dns dns_fh -d example.com --dnssleep 1200 +``` + +The `FH_API_TOKEN will be saved in `~/.acme.sh/account.conf` and will be reused when needed. # Use custom API If your API is not supported yet, you can write your own DNS API. @@ -917,4 +968,4 @@ See: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide # Use lexicon DNS API -https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api \ No newline at end of file +https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api diff --git a/dnsapi/dns_fh.sh b/dnsapi/dns_fh.sh new file mode 100644 index 00000000..97e09ad6 --- /dev/null +++ b/dnsapi/dns_fh.sh @@ -0,0 +1,179 @@ +#!/usr/bin/env sh +######################################################################## +# Futurehosting script for acme.sh +# +# Environment variables: +# +# - FH_API_TOKEN (your Futurehosting API Token) +# Note: If you do not have an API token, one can be generated at: +# https://my.futurehosting.net/api-token +# +# Author: Frank Laszlo + +FH_API_URL="https://my.futurehosting.com/" +FH_API_VERSION="0" + +# dns_futurehosting_add() - Add TXT record +# Usage: dns_futurehosting_add _acme-challenge.subdomain.domain.com "XyZ123..." +dns_futurehosting_add() { + host="${1}" + txtvalue="${2}" + + if ! _check_futurehosting_api_token; then + return 1 + fi + + _info "Using Futurehosting" + _debug "Calling: dns_futurehosting_add() '${host}' '${txtvalue}'" + + _debug "Detecting root zone" + if ! _get_root "${host}"; then + _err "Zone for domain does not exist." + return 1 + fi + _debug _zone_id "${_zone_id}" + _debug _sub_domain "${_sub_domain}" + _debug _domain "${_domain}" + + _post_data="{\"zone_id\": \"${_zone_id}\", \"type\": \"TXT\", \"host\": \"${host}\", \"target\": \"${txtvalue}\", \"ttl\": \"300\"}" + + if _rest POST "dns-record" "${_post_data}" && [ -n "${response}" ]; then + _record_id=$(printf "%s\n" "${response}" | _egrep_o "\"record_id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) + _debug _record_id "${_record_id}" + + if [ -z "$_record_id" ]; then + _err "Error adding the TXT record." + return 1 + fi + + _info "TXT record successfully added." + return 0 + fi + + return 1 +} + +# dns_futurehosting_rm() - Remove TXT record +# Usage: dns_futurehosting_rm _acme-challenge.subdomain.domain.com +dns_futurehosting_rm() { + host="${1}" + + if ! _check_futurehosting_api_token; then + return 1 + fi + + _info "Using Futurehosting" + _debug "Calling: dns_futurehosting_rm() '${host}'" + + _debug "Detecting root zone" + if ! _get_root "${host}"; then + _err "Zone for domain does not exist." + return 1 + fi + _debug _zone_id "${_zone_id}" + _debug _sub_domain "${_sub_domain}" + _debug _domain "${_domain}" + + _parameters="?zone_id=${_zone_id}" + + if _rest GET "dns-record" "${_parameters}" && [ -n "${response}" ]; then + response="$(echo "${response}" | tr -d "\n" | sed 's/^\[\(.*\)\]$/\1/' | sed -e 's/{"record_id":/|"record_id":/g' | sed 's/|/&{/g' | tr "|" "\n")" + + record="$(echo "${response}" | _egrep_o "{.*\"host\":\s*\"${_sub_domain}\".*}")" + if [ "${record}" ]; then + _record_id=$(printf "%s\n" "${record}" | _egrep_o "\"record_id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "${_record_id}" ]; then + _debug _record_id "${_record_id}" + + _rest DELETE "dns-record/${_record_id}" + + _info "TXT record successfully deleted." + return 0 + fi + + return 1 + fi + + return 0 + fi + + return 1 +} + +_check_futurehosting_api_token() { + if [ -z "${FH_API_TOKEN}" ]; then + FH_API_TOKEN="" + + _err "You have not defined your FH_API_TOKEN." + _err "Please create your token and try again." + _err "If you need to generate a new token, please visit:" + _err "https://portal.futurehosting.net/api-token" + + return 1 + fi + + _saveaccountconf FH_API_TOKEN "${FH_API_TOKEN}" +} + +_get_root() { + domain="${1}" + i=2 + p=1 + + if _rest GET "dns-zone"; then + response="$(echo "${response}" | tr -d "\n" | sed 's/^\[\(.*\)\]$/\1/' | sed -e 's/{"zone_id":/|"zone_id":/g' | sed 's/|/&{/g' | tr "|" "\n")" + + _debug response "${response}" + while true; do + h=$(printf "%s" "${domain}" | cut -d . -f $i-100) + _debug h "${h}" + if [ -z "${h}" ]; then + #not valid + return 1 + fi + + hostedzone="$(echo "${response}" | _egrep_o "{.*\"domain\":\s*\"${h}\".*}")" + if [ "${hostedzone}" ]; then + _zone_id=$(printf "%s\n" "${hostedzone}" | _egrep_o "\"zone_id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "${_zone_id}" ]; then + _sub_domain=$(printf "%s" "${domain}" | cut -d . -f 1-${p}) + _domain="${h}" + return 0 + fi + return 1 + fi + p=$i + i=$(_math "${i}" + 1) + done + fi + return 1 +} + +_rest() { + method="${1}" + ep="${2}" + data="${3}" + + _debug method "${method}" + _debug ep "${ep}" + + export _H1="Accept: application/json" + export _H2="Content-Type: application/json" + export _H3="Api-Version: ${FH_API_VERSION}" + export _H4="User-Agent: FH-ACME-CLIENT" + export _H5="Authorization: Bearer ${FH_API_TOKEN}" + + if [ "${method}" != "GET" ]; then + _debug data "${data}" + response="$(_post "${data}" "${FH_API_URL}${ep}" "" "${method}")" + else + response="$(_get "${FH_API_URL}${ep}${data}")" + fi + + if [ "${?}" != "0" ]; then + _err "error ${ep}" + return 1 + fi + _debug2 response "${response}" + return 0 +} diff --git a/dnsapi/dns_nexcess.sh b/dnsapi/dns_nexcess.sh new file mode 100644 index 00000000..a8c8c6a4 --- /dev/null +++ b/dnsapi/dns_nexcess.sh @@ -0,0 +1,179 @@ +#!/usr/bin/env sh +######################################################################## +# Nexcess script for acme.sh +# +# Environment variables: +# +# - NEXCESS_API_TOKEN (your Nexcess API Token) +# Note: If you do not have an API token, one can be generated at: +# https://portal.nexcess.net/api-token +# +# Author: Frank Laszlo + +NEXCESS_API_URL="https://portal.nexcess.net/" +NEXCESS_API_VERSION="0" + +# dns_nexcess_add() - Add TXT record +# Usage: dns_nexcess_add _acme-challenge.subdomain.domain.com "XyZ123..." +dns_nexcess_add() { + host="${1}" + txtvalue="${2}" + + if ! _check_nexcess_api_token; then + return 1 + fi + + _info "Using Nexcess" + _debug "Calling: dns_nexcess_add() '${host}' '${txtvalue}'" + + _debug "Detecting root zone" + if ! _get_root "${host}"; then + _err "Zone for domain does not exist." + return 1 + fi + _debug _zone_id "${_zone_id}" + _debug _sub_domain "${_sub_domain}" + _debug _domain "${_domain}" + + _post_data="{\"zone_id\": \"${_zone_id}\", \"type\": \"TXT\", \"host\": \"${host}\", \"target\": \"${txtvalue}\", \"ttl\": \"300\"}" + + if _rest POST "dns-record" "${_post_data}" && [ -n "${response}" ]; then + _record_id=$(printf "%s\n" "${response}" | _egrep_o "\"record_id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) + _debug _record_id "${_record_id}" + + if [ -z "$_record_id" ]; then + _err "Error adding the TXT record." + return 1 + fi + + _info "TXT record successfully added." + return 0 + fi + + return 1 +} + +# dns_nexcess_rm() - Remove TXT record +# Usage: dns_nexcess_rm _acme-challenge.subdomain.domain.com +dns_nexcess_rm() { + host="${1}" + + if ! _check_nexcess_api_token; then + return 1 + fi + + _info "Using Nexcess" + _debug "Calling: dns_nexcess_rm() '${host}'" + + _debug "Detecting root zone" + if ! _get_root "${host}"; then + _err "Zone for domain does not exist." + return 1 + fi + _debug _zone_id "${_zone_id}" + _debug _sub_domain "${_sub_domain}" + _debug _domain "${_domain}" + + _parameters="?zone_id=${_zone_id}" + + if _rest GET "dns-record" "${_parameters}" && [ -n "${response}" ]; then + response="$(echo "${response}" | tr -d "\n" | sed 's/^\[\(.*\)\]$/\1/' | sed -e 's/{"record_id":/|"record_id":/g' | sed 's/|/&{/g' | tr "|" "\n")" + + record="$(echo "${response}" | _egrep_o "{.*\"host\":\s*\"${_sub_domain}\".*}")" + if [ "${record}" ]; then + _record_id=$(printf "%s\n" "${record}" | _egrep_o "\"record_id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "${_record_id}" ]; then + _debug _record_id "${_record_id}" + + _rest DELETE "dns-record/${_record_id}" + + _info "TXT record successfully deleted." + return 0 + fi + + return 1 + fi + + return 0 + fi + + return 1 +} + +_check_nexcess_api_token() { + if [ -z "${NEXCESS_API_TOKEN}" ]; then + NEXCESS_API_TOKEN="" + + _err "You have not defined your NEXCESS_API_TOKEN." + _err "Please create your token and try again." + _err "If you need to generate a new token, please visit:" + _err "https://portal.nexcess.net/api-token" + + return 1 + fi + + _saveaccountconf NEXCESS_API_TOKEN "${NEXCESS_API_TOKEN}" +} + +_get_root() { + domain="${1}" + i=2 + p=1 + + if _rest GET "dns-zone"; then + response="$(echo "${response}" | tr -d "\n" | sed 's/^\[\(.*\)\]$/\1/' | sed -e 's/{"zone_id":/|"zone_id":/g' | sed 's/|/&{/g' | tr "|" "\n")" + + _debug response "${response}" + while true; do + h=$(printf "%s" "${domain}" | cut -d . -f $i-100) + _debug h "${h}" + if [ -z "${h}" ]; then + #not valid + return 1 + fi + + hostedzone="$(echo "${response}" | _egrep_o "{.*\"domain\":\s*\"${h}\".*}")" + if [ "${hostedzone}" ]; then + _zone_id=$(printf "%s\n" "${hostedzone}" | _egrep_o "\"zone_id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "${_zone_id}" ]; then + _sub_domain=$(printf "%s" "${domain}" | cut -d . -f 1-${p}) + _domain="${h}" + return 0 + fi + return 1 + fi + p=$i + i=$(_math "${i}" + 1) + done + fi + return 1 +} + +_rest() { + method="${1}" + ep="${2}" + data="${3}" + + _debug method "${method}" + _debug ep "${ep}" + + export _H1="Accept: application/json" + export _H2="Content-Type: application/json" + export _H3="Api-Version: ${NEXCESS_API_VERSION}" + export _H4="User-Agent: NEXCESS-ACME-CLIENT" + export _H5="Authorization: Bearer ${NEXCESS_API_TOKEN}" + + if [ "${method}" != "GET" ]; then + _debug data "${data}" + response="$(_post "${data}" "${NEXCESS_API_URL}${ep}" "" "${method}")" + else + response="$(_get "${NEXCESS_API_URL}${ep}${data}")" + fi + + if [ "${?}" != "0" ]; then + _err "error ${ep}" + return 1 + fi + _debug2 response "${response}" + return 0 +} diff --git a/dnsapi/dns_thermo.sh b/dnsapi/dns_thermo.sh new file mode 100644 index 00000000..16c437a5 --- /dev/null +++ b/dnsapi/dns_thermo.sh @@ -0,0 +1,179 @@ +#!/usr/bin/env sh +######################################################################## +# Thermo.io script for acme.sh +# +# Environment variables: +# +# - THERMO_API_TOKEN (your Thermo.io API Token) +# Note: If you do not have an API token, one can be generated at: +# https://portal.thermo.net/api-token +# +# Author: Frank Laszlo + +THERMO_API_URL="https://core.thermo.io/" +THERMO_API_VERSION="0" + +# dns_thermo_add() - Add TXT record +# Usage: dns_thermo_add _acme-challenge.subdomain.domain.com "XyZ123..." +dns_thermo_add() { + host="${1}" + txtvalue="${2}" + + if ! _check_thermo_api_token; then + return 1 + fi + + _info "Using Thermo.io" + _debug "Calling: dns_thermo_add() '${host}' '${txtvalue}'" + + _debug "Detecting root zone" + if ! _get_root "${host}"; then + _err "Zone for domain does not exist." + return 1 + fi + _debug _zone_id "${_zone_id}" + _debug _sub_domain "${_sub_domain}" + _debug _domain "${_domain}" + + _post_data="{\"zone_id\": \"${_zone_id}\", \"type\": \"TXT\", \"host\": \"${host}\", \"target\": \"${txtvalue}\", \"ttl\": \"300\"}" + + if _rest POST "dns-record" "${_post_data}" && [ -n "${response}" ]; then + _record_id=$(printf "%s\n" "${response}" | _egrep_o "\"record_id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) + _debug _record_id "${_record_id}" + + if [ -z "$_record_id" ]; then + _err "Error adding the TXT record." + return 1 + fi + + _info "TXT record successfully added." + return 0 + fi + + return 1 +} + +# dns_thermo_rm() - Remove TXT record +# Usage: dns_thermo_rm _acme-challenge.subdomain.domain.com +dns_thermo_rm() { + host="${1}" + + if ! _check_thermo_api_token; then + return 1 + fi + + _info "Using Thermo.io" + _debug "Calling: dns_thermo_rm() '${host}'" + + _debug "Detecting root zone" + if ! _get_root "${host}"; then + _err "Zone for domain does not exist." + return 1 + fi + _debug _zone_id "${_zone_id}" + _debug _sub_domain "${_sub_domain}" + _debug _domain "${_domain}" + + _parameters="?zone_id=${_zone_id}" + + if _rest GET "dns-record" "${_parameters}" && [ -n "${response}" ]; then + response="$(echo "${response}" | tr -d "\n" | sed 's/^\[\(.*\)\]$/\1/' | sed -e 's/{"record_id":/|"record_id":/g' | sed 's/|/&{/g' | tr "|" "\n")" + + record="$(echo "${response}" | _egrep_o "{.*\"host\":\s*\"${_sub_domain}\".*}")" + if [ "${record}" ]; then + _record_id=$(printf "%s\n" "${record}" | _egrep_o "\"record_id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "${_record_id}" ]; then + _debug _record_id "${_record_id}" + + _rest DELETE "dns-record/${_record_id}" + + _info "TXT record successfully deleted." + return 0 + fi + + return 1 + fi + + return 0 + fi + + return 1 +} + +_check_thermo_api_token() { + if [ -z "${THERMO_API_TOKEN}" ]; then + THERMO_API_TOKEN="" + + _err "You have not defined your THERMO_API_TOKEN." + _err "Please create your token and try again." + _err "If you need to generate a new token, please visit:" + _err "https://portal.thermo.net/api-token" + + return 1 + fi + + _saveaccountconf THERMO_API_TOKEN "${THERMO_API_TOKEN}" +} + +_get_root() { + domain="${1}" + i=2 + p=1 + + if _rest GET "dns-zone"; then + response="$(echo "${response}" | tr -d "\n" | sed 's/^\[\(.*\)\]$/\1/' | sed -e 's/{"zone_id":/|"zone_id":/g' | sed 's/|/&{/g' | tr "|" "\n")" + + _debug response "${response}" + while true; do + h=$(printf "%s" "${domain}" | cut -d . -f $i-100) + _debug h "${h}" + if [ -z "${h}" ]; then + #not valid + return 1 + fi + + hostedzone="$(echo "${response}" | _egrep_o "{.*\"domain\":\s*\"${h}\".*}")" + if [ "${hostedzone}" ]; then + _zone_id=$(printf "%s\n" "${hostedzone}" | _egrep_o "\"zone_id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "${_zone_id}" ]; then + _sub_domain=$(printf "%s" "${domain}" | cut -d . -f 1-${p}) + _domain="${h}" + return 0 + fi + return 1 + fi + p=$i + i=$(_math "${i}" + 1) + done + fi + return 1 +} + +_rest() { + method="${1}" + ep="${2}" + data="${3}" + + _debug method "${method}" + _debug ep "${ep}" + + export _H1="Accept: application/json" + export _H2="Content-Type: application/json" + export _H3="Api-Version: ${THERMO_API_VERSION}" + export _H4="User-Agent: THERMO-ACME-CLIENT" + export _H5="Authorization: Bearer ${THERMO_API_TOKEN}" + + if [ "${method}" != "GET" ]; then + _debug data "${data}" + response="$(_post "${data}" "${THERMO_API_URL}${ep}" "" "${method}")" + else + response="$(_get "${THERMO_API_URL}${ep}${data}")" + fi + + if [ "${?}" != "0" ]; then + _err "error ${ep}" + return 1 + fi + _debug2 response "${response}" + return 0 +}