Permalink
Browse files

Merge pull request #1939 from Neilpang/dev

sync
  • Loading branch information...
Neilpang committed Nov 29, 2018
2 parents 393d8b9 + 8ec1ea7 commit d0c97a589b264b5d991e0cd74f4ea51b44a7c8b7
Showing with 363 additions and 1 deletion.
  1. +1 −0 Dockerfile
  2. +2 −0 README.md
  3. +1 −1 acme.sh
  4. +40 −0 dnsapi/README.md
  5. +109 −0 dnsapi/dns_hostingde.sh
  6. +210 −0 dnsapi/dns_mydnsjp.sh
@@ -4,6 +4,7 @@ RUN apk update -f \
&& apk --no-cache add -f \
openssl \
coreutils \
bind-tools \
curl \
socat \
&& rm -rf /var/cache/apk/*
@@ -327,6 +327,8 @@ You don't have to do anything manually!
1. netcup DNS API (https://www.netcup.de)
1. GratisDNS.dk (https://gratisdns.dk)
1. Namecheap API (https://www.namecheap.com/)
1. MyDNS.JP API (https://www.mydns.jp/)
1. hosting.de (https://www.hosting.de)

And:

@@ -5485,7 +5485,7 @@ Parameters:
--server SERVER ACME Directory Resource URI. (default: https://acme-v01.api.letsencrypt.org/directory)
--accountconf Specifies a customized account config file.
--home Specifies the home dir for $PROJECT_NAME .
--home Specifies the home dir for $PROJECT_NAME.
--cert-home Specifies the home dir to save all the certs, only valid for '--install' command.
--config-home Specifies the home dir to save all the configurations.
--useragent Specifies the user agent string. it will be saved for future use too.
@@ -1014,6 +1014,46 @@ Now you can issue a certificate.
acme.sh --issue --dns dns_namecheap -d example.com -d *.example.com
```

## 54. Use MyDNS.JP API

First, register to MyDNS.JP and get MasterID and Password.

```
export MYDNSJP_MasterID=MasterID
export MYDNSJP_Password=Password
```

To issue a certificate:

```
acme.sh --issue --dns dns_mydnsjp -d example.com -d www.example.com
```
The `MYDNSJP_MasterID` and `MYDNSJP_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.

## 55. Use hosting.de API

Create an API key in your hosting.de account here: https://secure.hosting.de

The key needs the following rights:
- DNS_ZONES_EDIT
- DNS_ZONES_LIST

Set your API Key and endpoint:

```
export HOSTINGDE_APIKEY='xxx'
export HOSTINGDE_ENDPOINT='https://secure.hosting.de'
```

The plugin can also be used for the http.net API. http.net customers have to set endpoint to https://partner.http.net.

Ok, let's issue a cert now:
```
acme.sh --issue --dns dns_hostingde -d example.com -d *.example.com
```

The hosting.de API key and endpoint 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.
@@ -0,0 +1,109 @@
#!/usr/bin/env sh

# hosting.de API

# Values to export:
# export HOSTINGDE_ENDPOINT='https://secure.hosting.de'
# export HOSTINGDE_APIKEY='xxxxx'

######## Public functions #####################

dns_hostingde_add() {
fulldomain="${1}"
txtvalue="${2}"
_debug "Calling: _hostingde_addRecord() '${fulldomain}' '${txtvalue}'"
_hostingde_apiKey && _hostingde_getZoneConfig && _hostingde_addRecord
}

dns_hostingde_rm() {
fulldomain="${1}"
txtvalue="${2}"
_debug "Calling: _hostingde_removeRecord() '${fulldomain}' '${txtvalue}'"
_hostingde_apiKey && _hostingde_getZoneConfig && _hostingde_removeRecord
}

#################### own Private functions below ##################################

_hostingde_apiKey() {
HOSTINGDE_APIKEY="${HOSTINGDE_APIKEY:-$(_readaccountconf_mutable HOSTINGDE_APIKEY)}"
if [ -z "$HOSTINGDE_APIKEY" ] || [ -z "$HOSTINGDE_ENDPOINT" ]; then
HOSTINGDE_APIKEY=""
HOSTINGDE_ENDPOINT=""
_err "You haven't specified hosting.de API key or endpoint yet."
_err "Please create your key and try again."
return 1
fi

_saveaccountconf_mutable HOSTINGDE_APIKEY "$HOSTINGDE_APIKEY"
_saveaccountconf_mutable HOSTINGDE_ENDPOINT "$HOSTINGDE_ENDPOINT"
}

_hostingde_getZoneConfig() {
_info "Getting ZoneConfig"
curZone="${fulldomain#*.}"
returnCode=1
while _contains "${curZone}" "\\."; do
curData="{\"filter\":{\"field\":\"zoneName\",\"value\":\"${curZone}\"},\"limit\":1,\"authToken\":\"${HOSTINGDE_APIKEY}\"}"
curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneConfigsFind")"
_debug "Calling zoneConfigsFind: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneConfigsFind'"
_debug "Result of zoneConfigsFind: '$curResult'"
if _contains "${curResult}" '"status": "error"'; then
if _contains "${curResult}" '"code": 10109'; then
_err "The API-Key is invalid or could not be found"
else
_err "UNKNOWN API ERROR"
fi
returnCode=1
break
fi
if _contains "${curResult}" '"totalEntries": 1'; then
_info "Retrieved zone data."
_debug "Zone data: '${curResult}'"

# read ZoneConfigId for later update
zoneConfigId=$(echo "${curResult}" | _egrep_o '"id":.*' | cut -d ':' -f 2 | cut -d '"' -f 2)
_debug "zoneConfigId '${zoneConfigId}'"
returnCode=0
break
fi
curZone="${curZone#*.}"
done
if [ $returnCode -ne 0 ]; then
_info "ZoneEnd reached, Zone ${curZone} not found in hosting.de API"
fi
return $returnCode
}

_hostingde_addRecord() {
_info "Adding record to zone"
curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":\"${zoneConfigId}\"},\"recordsToAdd\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\",\"ttl\":3600}]}"
curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")"
_debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'"
_debug "Result of zoneUpdate: '$curResult'"
if _contains "${curResult}" '"status": "error"'; then
if _contains "${curResult}" '"code": 10109'; then
_err "The API-Key is invalid or could not be found"
else
_err "UNKNOWN API ERROR"
fi
return 1
fi
return 0
}

_hostingde_removeRecord() {
_info "Removing record from zone"
curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":\"${zoneConfigId}\"},\"recordsToDelete\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\"}]}"
curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")"
_debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'"
_debug "Result of zoneUpdate: '$curResult'"
if _contains "${curResult}" '"status": "error"'; then
if _contains "${curResult}" '"code": 10109'; then
_err "The API-Key is invalid or could not be found"
else
_err "UNKNOWN API ERROR"
fi
return 1
fi
return 0
}
@@ -0,0 +1,210 @@
#!/usr/bin/env sh

#Here is a api script for MyDNS.JP.
#This file name is "dns_mydnsjp.sh"
#So, here must be a method dns_mydnsjp_add()
#Which will be called by acme.sh to add the txt record to your api system.
#returns 0 means success, otherwise error.
#
#Author: epgdatacapbon
#Report Bugs here: https://github.com/epgdatacapbon/acme.sh
#
######## Public functions #####################

# Export MyDNS.JP MasterID and Password in following variables...
# MYDNSJP_MasterID=MasterID
# MYDNSJP_Password=Password

MYDNSJP_API="https://www.mydns.jp"

#Usage: dns_mydnsjp_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_mydnsjp_add() {
fulldomain=$1
txtvalue=$2

_info "Using mydnsjp"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"

# Load the credentials from the account conf file
MYDNSJP_MasterID="${MYDNSJP_MasterID:-$(_readaccountconf_mutable MYDNSJP_MasterID)}"
MYDNSJP_Password="${MYDNSJP_Password:-$(_readaccountconf_mutable MYDNSJP_Password)}"
if [ -z "$MYDNSJP_MasterID" ] || [ -z "$MYDNSJP_Password" ]; then
MYDNSJP_MasterID=""
MYDNSJP_Password=""
_err "You don't specify mydnsjp api MasterID and Password yet."
_err "Please export as MYDNSJP_MasterID / MYDNSJP_Password and try again."
return 1
fi

# Save the credentials to the account conf file
_saveaccountconf_mutable MYDNSJP_MasterID "$MYDNSJP_MasterID"
_saveaccountconf_mutable MYDNSJP_Password "$MYDNSJP_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"

if _mydnsjp_api "REGIST" "$_domain" "$txtvalue"; then
if printf -- "%s" "$response" | grep "OK." >/dev/null; then
_info "Added, OK"
return 0
else
_err "Add txt record error."
return 1
fi
fi
_err "Add txt record error."

return 1
}

#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_mydnsjp_rm() {
fulldomain=$1
txtvalue=$2

_info "Removing TXT record"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"

# Load the credentials from the account conf file
MYDNSJP_MasterID="${MYDNSJP_MasterID:-$(_readaccountconf_mutable MYDNSJP_MasterID)}"
MYDNSJP_Password="${MYDNSJP_Password:-$(_readaccountconf_mutable MYDNSJP_Password)}"
if [ -z "$MYDNSJP_MasterID" ] || [ -z "$MYDNSJP_Password" ]; then
MYDNSJP_MasterID=""
MYDNSJP_Password=""
_err "You don't specify mydnsjp api MasterID and Password yet."
_err "Please export as MYDNSJP_MasterID / MYDNSJP_Password and try again."
return 1
fi

_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"

if _mydnsjp_api "DELETE" "$_domain" "$txtvalue"; then
if printf -- "%s" "$response" | grep "OK." >/dev/null; then
_info "Deleted, OK"
return 0
else
_err "Delete txt record error."
return 1
fi
fi
_err "Delete txt record error."

return 1
}

#################### Private functions below ##################################
# _acme-challenge.www.domain.com
# returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
fulldomain=$1
i=2
p=1

# Get the root domain
_mydnsjp_retrieve_domain
if [ "$?" != "0" ]; then
# not valid
return 1
fi

while true; do
_domain=$(printf "%s" "$fulldomain" | cut -d . -f $i-100)

if [ -z "$_domain" ]; then
# not valid
return 1
fi

if [ "$_domain" = "$_root_domain" ]; then
_sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-$p)
return 0
fi

p=$i
i=$(_math "$i" + 1)
done

return 1
}

# Retrieve the root domain
# returns 0 success
_mydnsjp_retrieve_domain() {
_debug "Login to MyDNS.JP"

response="$(_post "masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password" "$MYDNSJP_API/?MENU=100")"
cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)"

# If cookies is not empty then logon successful
if [ -z "$cookie" ]; then
_err "Fail to get a cookie."
return 1
fi

_debug "Retrieve DOMAIN INFO page"

export _H1="Cookie:${cookie}"

response="$(_get "$MYDNSJP_API/?MENU=300")"

if [ "$?" != "0" ]; then
_err "Fail to retrieve DOMAIN INFO."
return 1
fi

_root_domain=$(echo "$response" | grep "DNSINFO\[domainname\]" | sed 's/^.*value="\([^"]*\)".*/\1/')

# Logout
response="$(_get "$MYDNSJP_API/?MENU=090")"

_debug _root_domain "$_root_domain"

if [ -z "$_root_domain" ]; then
_err "Fail to get the root domain."
return 1
fi

return 0
}

_mydnsjp_api() {
cmd=$1
domain=$2
txtvalue=$3

# Base64 encode the credentials
credentials=$(printf "%s:%s" "$MYDNSJP_MasterID" "$MYDNSJP_Password" | _base64)

# Construct the HTTP Authorization header
export _H1="Content-Type: application/x-www-form-urlencoded"
export _H2="Authorization: Basic ${credentials}"

response="$(_post "CERTBOT_DOMAIN=$domain&CERTBOT_VALIDATION=$txtvalue&EDIT_CMD=$cmd" "$MYDNSJP_API/directedit.html")"

if [ "$?" != "0" ]; then
_err "error $domain"
return 1
fi

_debug2 response "$response"

return 0
}

0 comments on commit d0c97a5

Please sign in to comment.