Sfoglia il codice sorgente

Merge pull request #5075 from acmesh-official/dev

sync
neil 1 anno fa
parent
commit
ebaa39b03f
6 ha cambiato i file con 282 aggiunte e 26 eliminazioni
  1. 12 7
      acme.sh
  2. 132 9
      deploy/haproxy.sh
  3. 19 0
      deploy/panos.sh
  4. 23 8
      dnsapi/dns_aws.sh
  5. 2 2
      dnsapi/dns_kappernet.sh
  6. 94 0
      dnsapi/dns_limacity.sh

+ 12 - 7
acme.sh

@@ -2396,13 +2396,18 @@ _migratedomainconf() {
   _old_key="$1"
   _new_key="$2"
   _b64encode="$3"
-  _value=$(_readdomainconf "$_old_key")
-  if [ -z "$_value" ]; then
-    return 1 # oldkey is not found
-  fi
-  _savedomainconf "$_new_key" "$_value" "$_b64encode"
+  _old_value=$(_readdomainconf "$_old_key")
   _cleardomainconf "$_old_key"
-  _debug "Domain config $_old_key has been migrated to $_new_key"
+  if [ -z "$_old_value" ]; then
+    return 1 # migrated failed: old value is empty
+  fi
+  _new_value=$(_readdomainconf "$_new_key")
+  if [ -n "$_new_value" ]; then
+    _debug "Domain config new key exists, old key $_old_key='$_old_value' has been removed."
+    return 1 # migrated failed: old value replaced by new value
+  fi
+  _savedomainconf "$_new_key" "$_old_value" "$_b64encode"
+  _debug "Domain config $_old_key has been migrated to $_new_key."
 }
 
 #_migratedeployconf   oldkey  newkey  base64encode
@@ -3768,7 +3773,7 @@ _regAccount() {
     eab_sign_t="$eab_protected64.$eab_payload64"
     _debug3 eab_sign_t "$eab_sign_t"
 
-    key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 multi | _hex_dump | tr -d ' ')"
+    key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 | _hex_dump | tr -d ' ')"
     _debug3 key_hex "$key_hex"
 
     eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace)

+ 132 - 9
deploy/haproxy.sh

@@ -36,6 +36,19 @@
 # Note: This functionality requires HAProxy was compiled against
 # a version of OpenSSL that supports this.
 #
+# export DEPLOY_HAPROXY_HOT_UPDATE="yes"
+# export DEPLOY_HAPROXY_STATS_SOCKET="UNIX:/run/haproxy/admin.sock"
+#
+# OPTIONAL: Deploy the certificate over the HAProxy stats socket without
+# needing to reload HAProxy. Default is "no".
+#
+# Require the socat binary. DEPLOY_HAPROXY_STATS_SOCKET variable uses the socat
+# address format.
+#
+# export DEPLOY_HAPROXY_MASTER_CLI="UNIX:/run/haproxy-master.sock"
+#
+# OPTIONAL: To use the master CLI with DEPLOY_HAPROXY_HOT_UPDATE="yes" instead
+# of a stats socket, use this variable.
 
 ########  Public functions #####################
 
@@ -46,6 +59,7 @@ haproxy_deploy() {
   _ccert="$3"
   _cca="$4"
   _cfullchain="$5"
+  _cmdpfx=""
 
   # Some defaults
   DEPLOY_HAPROXY_PEM_PATH_DEFAULT="/etc/haproxy"
@@ -53,6 +67,8 @@ haproxy_deploy() {
   DEPLOY_HAPROXY_BUNDLE_DEFAULT="no"
   DEPLOY_HAPROXY_ISSUER_DEFAULT="no"
   DEPLOY_HAPROXY_RELOAD_DEFAULT="true"
+  DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT="no"
+  DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT="UNIX:/run/haproxy/admin.sock"
 
   _debug _cdomain "${_cdomain}"
   _debug _ckey "${_ckey}"
@@ -86,6 +102,11 @@ haproxy_deploy() {
     _savedomainconf Le_Deploy_haproxy_pem_name "${Le_Deploy_haproxy_pem_name}"
   elif [ -z "${Le_Deploy_haproxy_pem_name}" ]; then
     Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}"
+    # We better not have '*' as the first character
+    if [ "${Le_Deploy_haproxy_pem_name%%"${Le_Deploy_haproxy_pem_name#?}"}" = '*' ]; then
+      # removes the first characters and add a _ instead
+      Le_Deploy_haproxy_pem_name="_${Le_Deploy_haproxy_pem_name#?}"
+    fi
   fi
 
   # BUNDLE is optional. If not provided then assume "${DEPLOY_HAPROXY_BUNDLE_DEFAULT}"
@@ -118,6 +139,36 @@ haproxy_deploy() {
     Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD_DEFAULT}"
   fi
 
+  # HOT_UPDATE is optional. If not provided then assume "${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}"
+  _getdeployconf DEPLOY_HAPROXY_HOT_UPDATE
+  _debug2 DEPLOY_HAPROXY_HOT_UPDATE "${DEPLOY_HAPROXY_HOT_UPDATE}"
+  if [ -n "${DEPLOY_HAPROXY_HOT_UPDATE}" ]; then
+    Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE}"
+    _savedomainconf Le_Deploy_haproxy_hot_update "${Le_Deploy_haproxy_hot_update}"
+  elif [ -z "${Le_Deploy_haproxy_hot_update}" ]; then
+    Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}"
+  fi
+
+  # STATS_SOCKET is optional. If not provided then assume "${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}"
+  _getdeployconf DEPLOY_HAPROXY_STATS_SOCKET
+  _debug2 DEPLOY_HAPROXY_STATS_SOCKET "${DEPLOY_HAPROXY_STATS_SOCKET}"
+  if [ -n "${DEPLOY_HAPROXY_STATS_SOCKET}" ]; then
+    Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET}"
+    _savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}"
+  elif [ -z "${Le_Deploy_haproxy_stats_socket}" ]; then
+    Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}"
+  fi
+
+  # MASTER_CLI is optional. No defaults are used. When the master CLI is used,
+  # all commands are sent with a prefix.
+  _getdeployconf DEPLOY_HAPROXY_MASTER_CLI
+  _debug2 DEPLOY_HAPROXY_MASTER_CLI "${DEPLOY_HAPROXY_MASTER_CLI}"
+  if [ -n "${DEPLOY_HAPROXY_MASTER_CLI}" ]; then
+    Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_MASTER_CLI}"
+    _savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}"
+    _cmdpfx="@1 " # command prefix used for master CLI only.
+  fi
+
   # Set the suffix depending if we are creating a bundle or not
   if [ "${Le_Deploy_haproxy_bundle}" = "yes" ]; then
     _info "Bundle creation requested"
@@ -142,12 +193,13 @@ haproxy_deploy() {
   _issuer="${_pem}.issuer"
   _ocsp="${_pem}.ocsp"
   _reload="${Le_Deploy_haproxy_reload}"
+  _statssock="${Le_Deploy_haproxy_stats_socket}"
 
   _info "Deploying PEM file"
   # Create a temporary PEM file
   _temppem="$(_mktemp)"
   _debug _temppem "${_temppem}"
-  cat "${_ccert}" "${_cca}" "${_ckey}" >"${_temppem}"
+  cat "${_ccert}" "${_cca}" "${_ckey}" | grep . >"${_temppem}"
   _ret="$?"
 
   # Check that we could create the temporary file
@@ -265,15 +317,86 @@ haproxy_deploy() {
     fi
   fi
 
-  # Reload HAProxy
-  _debug _reload "${_reload}"
-  eval "${_reload}"
-  _ret=$?
-  if [ "${_ret}" != "0" ]; then
-    _err "Error code ${_ret} during reload"
-    return ${_ret}
+  if [ "${Le_Deploy_haproxy_hot_update}" = "yes" ]; then
+    # set the socket name for messages
+    if [ -n "${_cmdpfx}" ]; then
+      _socketname="master CLI"
+    else
+      _socketname="stats socket"
+    fi
+
+    # Update certificate over HAProxy stats socket or master CLI.
+    if _exists socat; then
+      # look for the certificate on the stats socket, to chose between updating or creating one
+      _socat_cert_cmd="echo '${_cmdpfx}show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'"
+      _debug _socat_cert_cmd "${_socat_cert_cmd}"
+      eval "${_socat_cert_cmd}"
+      _ret=$?
+      if [ "${_ret}" != "0" ]; then
+        _newcert="1"
+        _info "Creating new certificate '${_pem}' over HAProxy ${_socketname}."
+        # certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate.
+        _socat_crtlist_show_cmd="echo '${_cmdpfx}show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'"
+        _debug _socat_crtlist_show_cmd "${_socat_crtlist_show_cmd}"
+        eval "${_socat_crtlist_show_cmd}"
+        _ret=$?
+        if [ "${_ret}" != "0" ]; then
+          _err "Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'"
+          return "${_ret}"
+        fi
+        # create a new certificate
+        _socat_new_cmd="echo '${_cmdpfx}new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'"
+        _debug _socat_new_cmd "${_socat_new_cmd}"
+        eval "${_socat_new_cmd}"
+        _ret=$?
+        if [ "${_ret}" != "0" ]; then
+          _err "Couldn't create '${_pem}' in haproxy"
+          return "${_ret}"
+        fi
+      else
+        _info "Update existing certificate '${_pem}' over HAProxy ${_socketname}."
+      fi
+      _socat_cert_set_cmd="echo -e '${_cmdpfx}set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -q 'Transaction created'"
+      _debug _socat_cert_set_cmd "${_socat_cert_set_cmd}"
+      eval "${_socat_cert_set_cmd}"
+      _ret=$?
+      if [ "${_ret}" != "0" ]; then
+        _err "Can't update '${_pem}' in haproxy"
+        return "${_ret}"
+      fi
+      _socat_cert_commit_cmd="echo '${_cmdpfx}commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'"
+      _debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}"
+      eval "${_socat_cert_commit_cmd}"
+      _ret=$?
+      if [ "${_ret}" != "0" ]; then
+        _err "Can't commit '${_pem}' in haproxy"
+        return ${_ret}
+      fi
+      if [ "${_newcert}" = "1" ]; then
+        # if this is a new certificate, it needs to be inserted into the crt-list`
+        _socat_cert_add_cmd="echo '${_cmdpfx}add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'"
+        _debug _socat_cert_add_cmd "${_socat_cert_add_cmd}"
+        eval "${_socat_cert_add_cmd}"
+        _ret=$?
+        if [ "${_ret}" != "0" ]; then
+          _err "Can't update '${_pem}' in haproxy"
+          return "${_ret}"
+        fi
+      fi
+    else
+      _err "'socat' is not available, couldn't update over ${_socketname}"
+    fi
   else
-    _info "Reload successful"
+    # Reload HAProxy
+    _debug _reload "${_reload}"
+    eval "${_reload}"
+    _ret=$?
+    if [ "${_ret}" != "0" ]; then
+      _err "Error code ${_ret} during reload"
+      return ${_ret}
+    else
+      _info "Reload successful"
+    fi
   fi
 
   return 0

+ 19 - 0
deploy/panos.sh

@@ -12,6 +12,9 @@
 #     export PANOS_USER=""    #User *MUST* have Commit and Import Permissions in XML API for Admin Role
 #     export PANOS_PASS=""
 #
+# OPTIONAL
+#    export PANOS_TEMPLATE="" #Template Name of panorama managed devices
+#
 # The script will automatically generate a new API key if
 # no key is found, or if a saved key has expired or is invalid.
 
@@ -78,6 +81,9 @@ deployer() {
       content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
       content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
       content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")"
+      if [ "$_panos_template" ]; then
+        content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template"
+      fi
     fi
     if [ "$type" = 'key' ]; then
       panos_url="${panos_url}?type=import"
@@ -87,6 +93,9 @@ deployer() {
       content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
       content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456"
       content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cdomain.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
+      if [ "$_panos_template" ]; then
+        content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template"
+      fi
     fi
     #Close multipart
     content="$content${nl}--$delim--${nl}${nl}"
@@ -173,10 +182,20 @@ panos_deploy() {
     unset _panos_key
   fi
 
+  # PANOS_TEMPLATE
+  if [ "$PANOS_TEMPLATE" ]; then
+    _debug "Detected ENV variable PANOS_TEMPLATE. Saving to file."
+    _savedeployconf PANOS_TEMPLATE "$PANOS_TEMPLATE" 1
+  else
+    _debug "Attempting to load variable PANOS_TEMPLATE from file."
+    _getdeployconf PANOS_TEMPLATE
+  fi
+
   #Store variables
   _panos_host=$PANOS_HOST
   _panos_user=$PANOS_USER
   _panos_pass=$PANOS_PASS
+  _panos_template=$PANOS_TEMPLATE
 
   #Test API Key if found.  If the key is invalid, the variable _panos_key will be unset.
   if [ "$_panos_host" ] && [ "$_panos_key" ]; then

+ 23 - 8
dnsapi/dns_aws.sh

@@ -145,7 +145,6 @@ dns_aws_rm() {
   fi
   _sleep 1
   return 1
-
 }
 
 ####################  Private functions below ##################################
@@ -207,24 +206,40 @@ _use_container_role() {
 }
 
 _use_instance_role() {
-  _url="http://169.254.169.254/latest/meta-data/iam/security-credentials/"
-  _debug "_url" "$_url"
-  if ! _get "$_url" true 1 | _head_n 1 | grep -Fq 200; then
+  _instance_role_name_url="http://169.254.169.254/latest/meta-data/iam/security-credentials/"
+
+  if _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 401; then
+    _debug "Using IMDSv2"
+    _token_url="http://169.254.169.254/latest/api/token"
+    export _H1="X-aws-ec2-metadata-token-ttl-seconds: 21600"
+    _token="$(_post "" "$_token_url" "" "PUT")"
+    _secure_debug3 "_token" "$_token"
+    if [ -z "$_token" ]; then
+      _debug "Unable to fetch IMDSv2 token from instance metadata"
+      return 1
+    fi
+    export _H1="X-aws-ec2-metadata-token: $_token"
+  fi
+
+  if ! _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 200; then
     _debug "Unable to fetch IAM role from instance metadata"
     return 1
   fi
-  _aws_role=$(_get "$_url" "" 1)
-  _debug "_aws_role" "$_aws_role"
-  _use_metadata "$_url$_aws_role"
+
+  _instance_role_name=$(_get "$_instance_role_name_url" "" 1)
+  _debug "_instance_role_name" "$_instance_role_name"
+  _use_metadata "$_instance_role_name_url$_instance_role_name" "$_token"
+
 }
 
 _use_metadata() {
+  export _H1="X-aws-ec2-metadata-token: $2"
   _aws_creds="$(
     _get "$1" "" 1 |
       _normalizeJson |
       tr '{,}' '\n' |
       while read -r _line; do
-        _key="$(echo "${_line%%:*}" | tr -d '"')"
+        _key="$(echo "${_line%%:*}" | tr -d '\"')"
         _value="${_line#*:}"
         _debug3 "_key" "$_key"
         _secure_debug3 "_value" "$_value"

+ 2 - 2
dnsapi/dns_kappernet.sh

@@ -41,7 +41,7 @@ dns_kappernet_add() {
   _debug _domain "DOMAIN: $_domain"
 
   _info "Trying to add TXT DNS Record"
-  data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%223600%22%2C%22prio%22%3A%22%22%7D"
+  data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%22300%22%2C%22prio%22%3A%22%22%7D"
   if _kappernet_api GET "action=new&subject=$_domain&data=$data"; then
 
     if _contains "$response" "{\"OK\":true"; then
@@ -81,7 +81,7 @@ dns_kappernet_rm() {
   _saveaccountconf_mutable KAPPERNETDNS_Secret "$KAPPERNETDNS_Secret"
 
   _info "Trying to remove the TXT Record: $fullhostname containing $txtvalue"
-  data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%223600%22%2C%22prio%22%3A%22%22%7D"
+  data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%22300%22%2C%22prio%22%3A%22%22%7D"
   if _kappernet_api GET "action=del&subject=$fullhostname&data=$data"; then
     if _contains "$response" "{\"OK\":true"; then
       return 0

+ 94 - 0
dnsapi/dns_limacity.sh

@@ -0,0 +1,94 @@
+#!/usr/bin/env sh
+
+# Created by Laraveluser
+#
+# Pass credentials before "acme.sh --issue --dns dns_limacity ..."
+# --
+# export LIMACITY_APIKEY="<API-KEY>"
+# --
+#
+# Pleas note: APIKEY must have following roles: dns.admin, domains.reader
+
+########  Public functions #####################
+
+LIMACITY_APIKEY="${LIMACITY_APIKEY:-$(_readaccountconf_mutable LIMACITY_APIKEY)}"
+AUTH=$(printf "%s" "api:$LIMACITY_APIKEY" | _base64 -w 0)
+export _H1="Authorization: Basic $AUTH"
+export _H2="Content-Type: application/json"
+APIBASE=https://www.lima-city.de/usercp
+
+#Usage: dns_limacity_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_limacity_add() {
+  _debug LIMACITY_APIKEY "$LIMACITY_APIKEY"
+  if [ "$LIMACITY_APIKEY" = "" ]; then
+    _err "No Credentials given"
+    return 1
+  fi
+
+  # save the dns server and key to the account conf file.
+  _saveaccountconf_mutable LIMACITY_APIKEY "${LIMACITY_APIKEY}"
+
+  fulldomain=$1
+  txtvalue=$2
+  if ! _lima_get_domain_id "$fulldomain"; then return 1; fi
+
+  msg=$(_post "{\"nameserver_record\":{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"${txtvalue}\",\"ttl\":60}}" "${APIBASE}/domains/${LIMACITY_DOMAINID}/records.json" "" "POST")
+  _debug "$msg"
+
+  if [ "$(echo "$msg" | _egrep_o "\"status\":\"ok\"")" = "" ]; then
+    _err "$msg"
+    return 1
+  fi
+
+  return 0
+}
+
+#Usage: dns_limacity_rm   _acme-challenge.www.domain.com
+dns_limacity_rm() {
+
+  fulldomain=$1
+  txtvalue=$2
+  if ! _lima_get_domain_id "$fulldomain"; then return 1; fi
+
+  for recordId in $(_get "${APIBASE}/domains/${LIMACITY_DOMAINID}/records.json" | _egrep_o "{\"id\":[0-9]*[^}]*,\"name\":\"${fulldomain}\"" | _egrep_o "[0-9]*"); do
+    _post "" "${APIBASE}/domains/${LIMACITY_DOMAINID}/records/${recordId}" "" "DELETE"
+  done
+
+  return 0
+}
+
+####################  Private functions below ##################################
+
+_lima_get_domain_id() {
+  domain="$1"
+  _debug "$domain"
+  i=2
+  p=1
+
+  domains=$(_get "${APIBASE}/domains.json")
+  if [ "$(echo "$domains" | _egrep_o "\{.*""domains""")" ]; then
+    response="$(echo "$domains" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
+    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 "\{.*""unicode_fqdn""[^,]+""$h"".*\}")"
+      if [ "$hostedzone" ]; then
+        LIMACITY_DOMAINID=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+        if [ "$LIMACITY_DOMAINID" ]; 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
+}