Browse Source

sync (#2127)

* Support for MyDevil.net (#2076)

support mydevil

* Fix verification for namecheap domains not *owned* by the calling user (#2106)

* Peb (#2126)

* support pebble
* support async finalize order

* add Pebble
neil 6 years ago
parent
commit
693d692a47
7 changed files with 307 additions and 14 deletions
  1. 2 0
      README.md
  2. 74 14
      acme.sh
  3. 10 0
      deploy/README.md
  4. 59 0
      deploy/mydevil.sh
  5. 20 0
      dnsapi/README.md
  6. 97 0
      dnsapi/dns_mydevil.sh
  7. 45 0
      dnsapi/dns_namecheap.sh

+ 2 - 0
README.md

@@ -74,6 +74,7 @@ https://github.com/Neilpang/acmetest
 
 - Letsencrypt.org CA(default)
 - [BuyPass.com CA](https://github.com/Neilpang/acme.sh/wiki/BuyPass.com-CA)
+- [Pebble strict Mode](https://github.com/letsencrypt/pebble)
 
 # Supported modes
 
@@ -356,6 +357,7 @@ You don't have to do anything manually!
 1. Futurehosting API (https://www.futurehosting.com)
 1. Rackspace Cloud DNS (https://www.rackspace.com)
 1. Online.net API (https://online.net/)
+1. MyDevil.net (https://www.mydevil.net/)
 
 And:
 

+ 74 - 14
acme.sh

@@ -1827,23 +1827,29 @@ _send_signed_request() {
         nonceurl="$ACME_NEW_NONCE"
         if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type"; then
           _headers="$(cat "$HTTP_HEADER")"
+          _debug2 _headers "$_headers"
+          _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
         fi
       fi
-      if [ -z "$_headers" ]; then
+      if [ -z "$_CACHED_NONCE" ]; then
         _debug2 "Get nonce with GET. ACME_DIRECTORY" "$ACME_DIRECTORY"
         nonceurl="$ACME_DIRECTORY"
         _headers="$(_get "$nonceurl" "onlyheader")"
+        _debug2 _headers "$_headers"
+        _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
       fi
-
+      if [ -z "$_CACHED_NONCE" ] && [ "$ACME_NEW_NONCE" ]; then
+        _debug2 "Get nonce with GET. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
+        nonceurl="$ACME_NEW_NONCE"
+        _headers="$(_get "$nonceurl" "onlyheader")"
+        _debug2 _headers "$_headers"
+        _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
+      fi
+      _debug2 _CACHED_NONCE "$_CACHED_NONCE"
       if [ "$?" != "0" ]; then
         _err "Can not connect to $nonceurl to get nonce."
         return 1
       fi
-
-      _debug2 _headers "$_headers"
-
-      _CACHED_NONCE="$(echo "$_headers" | grep "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
-      _debug2 _CACHED_NONCE "$_CACHED_NONCE"
     else
       _debug2 "Use _CACHED_NONCE" "$_CACHED_NONCE"
     fi
@@ -2060,6 +2066,7 @@ _clearcaconf() {
 _startserver() {
   content="$1"
   ncaddr="$2"
+  _debug "content" "$content"
   _debug "ncaddr" "$ncaddr"
 
   _debug "startserver: $$"
@@ -2086,8 +2093,14 @@ _startserver() {
     SOCAT_OPTIONS="$SOCAT_OPTIONS,bind=${ncaddr}"
   fi
 
+  _content_len="$(printf "%s" "$content" | wc -c)"
+  _debug _content_len "$_content_len"
   _debug "_NC" "$_NC $SOCAT_OPTIONS"
-  $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; echo HTTP/1.0 200 OK; echo ; echo  $content; echo;" &
+  $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; \
+echo 'HTTP/1.0 200 OK'; \
+echo 'Content-Length\: $_content_len'; \
+echo ''; \
+printf '$content';" &
   serverproc="$!"
 }
 
@@ -3062,6 +3075,7 @@ _on_before_issue() {
       _info "Standalone mode."
       if [ -z "$Le_HTTPPort" ]; then
         Le_HTTPPort=80
+        _cleardomainconf "Le_HTTPPort"
       else
         _savedomainconf "Le_HTTPPort" "$Le_HTTPPort"
       fi
@@ -3269,7 +3283,7 @@ _regAccount() {
   fi
 
   _debug2 responseHeaders "$responseHeaders"
-  _accUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
+  _accUri="$(echo "$responseHeaders" | grep -i "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
   _debug "_accUri" "$_accUri"
   if [ -z "$_accUri" ]; then
     _err "Can not find account id url."
@@ -3435,7 +3449,7 @@ __trigger_validation() {
   _t_vtype="$3"
   _debug2 _t_vtype "$_t_vtype"
   if [ "$ACME_VERSION" = "2" ]; then
-    _send_signed_request "$_t_url" "{\"keyAuthorization\": \"$_t_key_authz\"}"
+    _send_signed_request "$_t_url" "{}"
   else
     _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"type\": \"$_t_vtype\", \"keyAuthorization\": \"$_t_key_authz\"}"
   fi
@@ -4205,20 +4219,66 @@ $_authorizations_map"
   der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)"
 
   if [ "$ACME_VERSION" = "2" ]; then
+    _info "Lets finalize the order, Le_OrderFinalize: $Le_OrderFinalize"
     if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then
       _err "Sign failed."
       _on_issue_err "$_post_hook"
       return 1
     fi
     if [ "$code" != "200" ]; then
-      _err "Sign failed, code is not 200."
+      _err "Sign failed, finalize code is not 200."
       _err "$response"
       _on_issue_err "$_post_hook"
       return 1
     fi
-    Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
+    Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
+    if [ -z "$Le_LinkOrder" ]; then
+      _err "Sign error, can not get order link location header"
+      _err "responseHeaders" "$responseHeaders"
+      _on_issue_err "$_post_hook"
+      return 1
+    fi
+    _savedomainconf "Le_LinkOrder" "$Le_LinkOrder"
+
+    _link_cert_retry=0
+    _MAX_CERT_RETRY=5
+    while [ -z "$Le_LinkCert" ] && [ "$_link_cert_retry" -lt "$_MAX_CERT_RETRY" ]; do
+      if _contains "$response" "\"status\":\"valid\""; then
+        _debug "Order status is valid."
+        Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
+        _debug Le_LinkCert "$Le_LinkCert"
+        if [ -z "$Le_LinkCert" ]; then
+          _err "Sign error, can not find Le_LinkCert"
+          _err "$response"
+          _on_issue_err "$_post_hook"
+          return 1
+        fi
+        break
+      elif _contains "$response" "\"processing\""; then
+        _info "Order status is processing, lets sleep and retry."
+        _sleep 2
+      else
+        _err "Sign error, wrong status"
+        _err "$response"
+        _on_issue_err "$_post_hook"
+        return 1
+      fi
+      if ! _send_signed_request "$Le_LinkOrder"; then
+        _err "Sign failed, can not post to Le_LinkOrder cert:$Le_LinkOrder."
+        _err "$response"
+        _on_issue_err "$_post_hook"
+        return 1
+      fi
+      _link_cert_retry="$(_math $_link_cert_retry + 1)"
+    done
 
-    _tempSignedResponse="$response"
+    if [ -z "$Le_LinkCert" ]; then
+      _err "Sign failed, can not get Le_LinkCert, retry time limit."
+      _err "$response"
+      _on_issue_err "$_post_hook"
+      return 1
+    fi
+    _info "Download cert, Le_LinkCert: $Le_LinkCert"
     if ! _send_signed_request "$Le_LinkCert"; then
       _err "Sign failed, can not download cert:$Le_LinkCert."
       _err "$response"
@@ -4237,7 +4297,7 @@ $_authorizations_map"
       _end_n="$(_math $_end_n + 1)"
       sed -n "${_end_n},9999p" "$CERT_FULLCHAIN_PATH" >"$CA_CERT_PATH"
     fi
-    response="$_tempSignedResponse"
+
   else
     if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then
       _err "Sign failed. $response"

+ 10 - 0
deploy/README.md

@@ -381,3 +381,13 @@ you want to update:
 $ export QINIU_CDN_DOMAIN="cdn.example.com"
 $ acme.sh --deploy -d example.com --deploy-hook qiniu
 ```
+
+## 14. Deploy your cert on MyDevil.net
+
+Once you have acme.sh installed and certificate issued (see info in [DNS API](../dnsapi/README.md#61-use-mydevilnet)), you can install it by following command:
+
+```sh
+acme.sh --deploy --deploy-hook mydevil -d example.com
+```
+
+That will remove old certificate and install new one.

+ 59 - 0
deploy/mydevil.sh

@@ -0,0 +1,59 @@
+#!/usr/bin/env sh
+
+# MyDevil.net API (2019-02-03)
+#
+# MyDevil.net already supports automatic Let's Encrypt certificates,
+# except for wildcard domains.
+#
+# This script depends on `devil` command that MyDevil.net provides,
+# which means that it works only on server side.
+#
+# Author: Marcin Konicki <https://ahwayakchih.neoni.net>
+#
+########  Public functions #####################
+
+# Usage: mydevil_deploy domain keyfile certfile cafile fullchain
+mydevil_deploy() {
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+  ip=""
+
+  _debug _cdomain "$_cdomain"
+  _debug _ckey "$_ckey"
+  _debug _ccert "$_ccert"
+  _debug _cca "$_cca"
+  _debug _cfullchain "$_cfullchain"
+
+  if ! _exists "devil"; then
+    _err "Could not find 'devil' command."
+    return 1
+  fi
+
+  ip=$(mydevil_get_ip "$_cdomain")
+  if [ -z "$ip" ]; then
+    _err "Could not find IP for domain $_cdomain."
+    return 1
+  fi
+
+  # Delete old certificate first
+  _info "Removing old certificate for $_cdomain at $ip"
+  devil ssl www del "$ip" "$_cdomain"
+
+  # Add new certificate
+  _info "Adding new certificate for $_cdomain at $ip"
+  devil ssl www add "$ip" "$_cfullchain" "$_ckey" "$_cdomain" || return 1
+
+  return 0
+}
+
+####################  Private functions below ##################################
+
+# Usage: ip=$(mydevil_get_ip domain.com)
+#        echo $ip
+mydevil_get_ip() {
+  devil dns list "$1" | cut -w -s -f 3,7 | grep "^A$(printf '\t')" | cut -w -s -f 2 || return 1
+  return 0
+}

+ 20 - 0
dnsapi/README.md

@@ -1259,6 +1259,26 @@ acme.sh --issue --dns dns_online -d example.com -d www.example.com
 
 `ONLINE_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
+## 66. Use MyDevil.net
+
+Make sure that you can execute own binaries:
+
+```sh
+devil binexec on
+```
+
+Install acme.sh, or simply `git clone` it into some directory on your MyDevil host account (in which case you should link to it from your `~/bin` directory).
+
+If you're not using private IP and depend on default IP provided by host, you may want to edit `crontab` too, and make sure that `acme.sh --cron` is run also after reboot (you can find out how to do that on their wiki pages).
+
+To issue a new certificate, run:
+
+```sh
+acme.sh --issue --dns dns_mydevil -d example.com -d *.example.com
+```
+
+After certificate is ready, you can install it with [deploy command](../deploy/README.md#14-deploy-your-cert-on-mydevilnet).
+
 # Use custom API
 
 If your API is not supported yet, you can write your own DNS API.

+ 97 - 0
dnsapi/dns_mydevil.sh

@@ -0,0 +1,97 @@
+#!/usr/bin/env sh
+
+# MyDevil.net API (2019-02-03)
+#
+# MyDevil.net already supports automatic Let's Encrypt certificates,
+# except for wildcard domains.
+#
+# This script depends on `devil` command that MyDevil.net provides,
+# which means that it works only on server side.
+#
+# Author: Marcin Konicki <https://ahwayakchih.neoni.net>
+#
+########  Public functions #####################
+
+#Usage: dns_mydevil_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_mydevil_add() {
+  fulldomain=$1
+  txtvalue=$2
+  domain=""
+
+  if ! _exists "devil"; then
+    _err "Could not find 'devil' command."
+    return 1
+  fi
+
+  _info "Using mydevil"
+
+  domain=$(mydevil_get_domain "$fulldomain")
+  if [ -z "$domain" ]; then
+    _err "Invalid domain name: could not find root domain of $fulldomain."
+    return 1
+  fi
+
+  # No need to check if record name exists, `devil` always adds new record.
+  # In worst case scenario, we end up with multiple identical records.
+
+  _info "Adding $fulldomain record for domain $domain"
+  if devil dns add "$domain" "$fulldomain" TXT "$txtvalue"; then
+    _info "Successfully added TXT record, ready for validation."
+    return 0
+  else
+    _err "Unable to add DNS record."
+    return 1
+  fi
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_mydevil_rm() {
+  fulldomain=$1
+  txtvalue=$2
+  domain=""
+
+  if ! _exists "devil"; then
+    _err "Could not find 'devil' command."
+    return 1
+  fi
+
+  _info "Using mydevil"
+
+  domain=$(mydevil_get_domain "$fulldomain")
+  if [ -z "$domain" ]; then
+    _err "Invalid domain name: could not find root domain of $fulldomain."
+    return 1
+  fi
+
+  # catch one or more numbers
+  num='[0-9][0-9]*'
+  # catch one or more whitespace
+  w=$(printf '[\t ][\t ]*')
+  # catch anything, except newline
+  any='.*'
+  # filter to make sure we do not delete other records
+  validRecords="^${num}${w}${fulldomain}${w}TXT${w}${any}${txtvalue}$"
+  for id in $(devil dns list "$domain" | tail -n+2 | grep "${validRecords}" | cut -w -s -f 1); do
+    _info "Removing record $id from domain $domain"
+    devil dns del "$domain" "$id" || _err "Could not remove DNS record."
+  done
+}
+
+####################  Private functions below ##################################
+
+# Usage: domain=$(mydevil_get_domain "_acme-challenge.www.domain.com" || _err "Invalid domain name")
+#        echo $domain
+mydevil_get_domain() {
+  fulldomain=$1
+  domain=""
+
+  for domain in $(devil dns list | cut -w -s -f 1 | tail -n+2); do
+    if _endswith "$fulldomain" "$domain"; then
+      printf -- "%s" "$domain"
+      return 0
+    fi
+  done
+
+  return 1
+}

+ 45 - 0
dnsapi/dns_namecheap.sh

@@ -76,6 +76,22 @@ dns_namecheap_rm() {
 # _sub_domain=_acme-challenge.www
 # _domain=domain.com
 _get_root() {
+  fulldomain=$1
+
+  if ! _get_root_by_getList "$fulldomain"; then
+    _debug "Failed domain lookup via domains.getList api call. Trying domain lookup via domains.dns.getHosts api."
+    # The above "getList" api will only return hosts *owned* by the calling user. However, if the calling
+    # user is not the owner, but still has administrative rights, we must query the getHosts api directly.
+    # See this comment and the official namecheap response: http://disq.us/p/1q6v9x9
+    if ! _get_root_by_getHosts "$fulldomain"; then
+      return 1
+    fi
+  fi
+
+  return 0
+}
+
+_get_root_by_getList() {
   domain=$1
 
   if ! _namecheap_post "namecheap.domains.getList"; then
@@ -94,6 +110,10 @@ _get_root() {
       #not valid
       return 1
     fi
+    if ! _contains "$h" "\\."; then
+      #not valid
+      return 1
+    fi
 
     if ! _contains "$response" "$h"; then
       _debug "$h not found"
@@ -108,6 +128,31 @@ _get_root() {
   return 1
 }
 
+_get_root_by_getHosts() {
+  i=100
+  p=99
+
+  while [ $p -ne 0 ]; do
+
+    h=$(printf "%s" "$1" | cut -d . -f $i-100)
+    if [ -n "$h" ]; then
+      if _contains "$h" "\\."; then
+        _debug h "$h"
+        if _namecheap_set_tld_sld "$h"; then
+          _sub_domain=$(printf "%s" "$1" | cut -d . -f 1-$p)
+          _domain="$h"
+          return 0
+        else
+          _debug "$h not found"
+        fi
+      fi
+    fi
+    i="$p"
+    p=$(_math "$p" - 1)
+  done
+  return 1
+}
+
 _namecheap_set_publicip() {
 
   if [ -z "$NAMECHEAP_SOURCEIP" ]; then