diff --git a/.github/workflows/FreeBSD.yml b/.github/workflows/FreeBSD.yml index dd80a0b9..407a9e6d 100644 --- a/.github/workflows/FreeBSD.yml +++ b/.github/workflows/FreeBSD.yml @@ -5,14 +5,14 @@ on: - '*' paths: - '*.sh' - - '**.yml' + - '.github/workflows/FreeBSD.yml' pull_request: branches: - dev paths: - '*.sh' - - '**.yml' + - '.github/workflows/FreeBSD.yml' jobs: diff --git a/.github/workflows/Linux.yml b/.github/workflows/Linux.yml index 6d4dcf7c..c4ec07c4 100644 --- a/.github/workflows/Linux.yml +++ b/.github/workflows/Linux.yml @@ -5,14 +5,14 @@ on: - '*' paths: - '*.sh' - - '**.yml' + - '.github/workflows/Linux.yml' pull_request: branches: - dev paths: - '*.sh' - - '**.yml' + - '.github/workflows/Linux.yml' diff --git a/.github/workflows/MacOS.yml b/.github/workflows/MacOS.yml index b03bec0c..5ceeba7a 100644 --- a/.github/workflows/MacOS.yml +++ b/.github/workflows/MacOS.yml @@ -5,14 +5,14 @@ on: - '*' paths: - '*.sh' - - '**.yml' + - '.github/workflows/MacOS.yml' pull_request: branches: - dev paths: - '*.sh' - - '**.yml' + - '.github/workflows/MacOS.yml' jobs: diff --git a/.github/workflows/PebbleStrict.yml b/.github/workflows/PebbleStrict.yml index fee41feb..ea8e723d 100644 --- a/.github/workflows/PebbleStrict.yml +++ b/.github/workflows/PebbleStrict.yml @@ -5,13 +5,13 @@ on: - '*' paths: - '*.sh' - - '**.yml' + - '.github/workflows/PebbleStrict.yml' pull_request: branches: - dev paths: - '*.sh' - - '**.yml' + - '.github/workflows/PebbleStrict.yml' jobs: PebbleStrict: diff --git a/.github/workflows/Solaris.yml b/.github/workflows/Solaris.yml index ad976f59..c3c756bb 100644 --- a/.github/workflows/Solaris.yml +++ b/.github/workflows/Solaris.yml @@ -5,14 +5,14 @@ on: - '*' paths: - '*.sh' - - '**.yml' + - '.github/workflows/Solaris.yml' pull_request: branches: - dev paths: - '*.sh' - - '**.yml' + - '.github/workflows/Solaris.yml' jobs: diff --git a/.github/workflows/Ubuntu.yml b/.github/workflows/Ubuntu.yml index 1ec50189..d3d66e19 100644 --- a/.github/workflows/Ubuntu.yml +++ b/.github/workflows/Ubuntu.yml @@ -5,14 +5,14 @@ on: - '*' paths: - '*.sh' - - '**.yml' + - '.github/workflows/Ubuntu.yml' pull_request: branches: - dev paths: - '*.sh' - - '**.yml' + - '.github/workflows/Ubuntu.yml' jobs: @@ -28,6 +28,7 @@ jobs: CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" CA: "ZeroSSL RSA Domain Secure Site CA" CA_EMAIL: "githubtest@acme.sh" + runs-on: ubuntu-latest env: TEST_LOCAL: 1 @@ -35,6 +36,7 @@ jobs: CA_ECDSA: ${{ matrix.CA_ECDSA }} CA: ${{ matrix.CA }} CA_EMAIL: ${{ matrix.CA_EMAIL }} + NO_ECC_384: ${{ matrix.NO_ECC_384 }} steps: - uses: actions/checkout@v2 - name: Install tools diff --git a/.github/workflows/Windows.yml b/.github/workflows/Windows.yml index 0219eaa5..91605a52 100644 --- a/.github/workflows/Windows.yml +++ b/.github/workflows/Windows.yml @@ -5,14 +5,14 @@ on: - '*' paths: - '*.sh' - - '**.yml' + - '.github/workflows/Windows.yml' pull_request: branches: - dev paths: - '*.sh' - - '**.yml' + - '.github/workflows/Windows.yml' jobs: diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 238fde3a..0c3aec0a 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -6,6 +6,11 @@ on: - '*' tags: - '*' + paths: + - '**.sh' + - "Dockerfile" + - '.github/workflows/dockerhub.yml' + jobs: CheckToken: diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index b22a2fd8..940a187d 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -5,13 +5,13 @@ on: - '*' paths: - '**.sh' - - '**.yml' + - '.github/workflows/shellcheck.yml' pull_request: branches: - dev paths: - '**.sh' - - '**.yml' + - '.github/workflows/shellcheck.yml' jobs: ShellCheck: diff --git a/acme.sh b/acme.sh index 681632fa..0dd8b678 100755 --- a/acme.sh +++ b/acme.sh @@ -2540,12 +2540,18 @@ _initAPI() { _api_server="${1:-$ACME_DIRECTORY}" _debug "_init api for server: $_api_server" - if [ -z "$ACME_NEW_ACCOUNT" ]; then + MAX_API_RETRY_TIMES=10 + _sleep_retry_sec=10 + _request_retry_times=0 + while [ -z "$ACME_NEW_ACCOUNT" ] && [ "${_request_retry_times}" -lt "$MAX_API_RETRY_TIMES" ]; do + _request_retry_times=$(_math "$_request_retry_times" + 1) response=$(_get "$_api_server") if [ "$?" != "0" ]; then _debug2 "response" "$response" - _err "Can not init api for: $_api_server." - return 1 + _info "Can not init api for: $_api_server." + _info "Sleep $_sleep_retry_sec and retry." + _sleep "$_sleep_retry_sec" + continue fi response=$(echo "$response" | _json_decode) _debug2 "response" "$response" @@ -2578,8 +2584,14 @@ _initAPI() { _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT" _debug "ACME_AGREEMENT" "$ACME_AGREEMENT" _debug "ACME_NEW_NONCE" "$ACME_NEW_NONCE" - - fi + if [ "$ACME_NEW_ACCOUNT" ] && [ "$ACME_NEW_ORDER" ]; then + return 0 + fi + _info "Sleep $_sleep_retry_sec and retry." + _sleep "$_sleep_retry_sec" + done + _err "Can not init api, for $_api_server" + return 1 } #[domain] [keylength or isEcc flag] diff --git a/deploy/consul.sh b/deploy/consul.sh new file mode 100644 index 00000000..f93fb452 --- /dev/null +++ b/deploy/consul.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env sh + +# Here is a script to deploy cert to hashicorp consul using curl +# (https://www.consul.io/) +# +# it requires following environment variables: +# +# CONSUL_PREFIX - this contains the prefix path in consul +# CONSUL_HTTP_ADDR - consul requires this to find your consul server +# +# additionally, you need to ensure that CONSUL_HTTP_TOKEN is available +# to access the consul server + +#returns 0 means success, otherwise error. + +######## Public functions ##################### + +#domain keyfile certfile cafile fullchain +consul_deploy() { + + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + # validate required env vars + _getdeployconf CONSUL_PREFIX + if [ -z "$CONSUL_PREFIX" ]; then + _err "CONSUL_PREFIX needs to be defined (contains prefix path in vault)" + return 1 + fi + _savedeployconf CONSUL_PREFIX "$CONSUL_PREFIX" + + _getdeployconf CONSUL_HTTP_ADDR + if [ -z "$CONSUL_HTTP_ADDR" ]; then + _err "CONSUL_HTTP_ADDR needs to be defined (contains consul connection address)" + return 1 + fi + _savedeployconf CONSUL_HTTP_ADDR "$CONSUL_HTTP_ADDR" + + CONSUL_CMD=$(command -v consul) + + # force CLI, but the binary does not exist => error + if [ -n "$USE_CLI" ] && [ -z "$CONSUL_CMD" ]; then + _err "Cannot find the consul binary!" + return 1 + fi + + # use the CLI first + if [ -n "$USE_CLI" ] || [ -n "$CONSUL_CMD" ]; then + _info "Found consul binary, deploying with CLI" + consul_deploy_cli "$CONSUL_CMD" "$CONSUL_PREFIX" + else + _info "Did not find consul binary, deploying with API" + consul_deploy_api "$CONSUL_HTTP_ADDR" "$CONSUL_PREFIX" "$CONSUL_HTTP_TOKEN" + fi +} + +consul_deploy_api() { + CONSUL_HTTP_ADDR="$1" + CONSUL_PREFIX="$2" + CONSUL_HTTP_TOKEN="$3" + + URL="$CONSUL_HTTP_ADDR/v1/kv/$CONSUL_PREFIX" + export _H1="X-Consul-Token: $CONSUL_HTTP_TOKEN" + + if [ -n "$FABIO" ]; then + _post "$(cat "$_cfullchain")" "$URL/${_cdomain}-cert.pem" '' "PUT" || return 1 + _post "$(cat "$_ckey")" "$URL/${_cdomain}-key.pem" '' "PUT" || return 1 + else + _post "$(cat "$_ccert")" "$URL/${_cdomain}/cert.pem" '' "PUT" || return 1 + _post "$(cat "$_ckey")" "$URL/${_cdomain}/cert.key" '' "PUT" || return 1 + _post "$(cat "$_cca")" "$URL/${_cdomain}/chain.pem" '' "PUT" || return 1 + _post "$(cat "$_cfullchain")" "$URL/${_cdomain}/fullchain.pem" '' "PUT" || return 1 + fi +} + +consul_deploy_cli() { + CONSUL_CMD="$1" + CONSUL_PREFIX="$2" + + if [ -n "$FABIO" ]; then + $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}-cert.pem" @"$_cfullchain" || return 1 + $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}-key.pem" @"$_ckey" || return 1 + else + $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1 + $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1 + $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1 + $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1 + fi +} diff --git a/dnsapi/dns_azion.sh b/dnsapi/dns_azion.sh new file mode 100644 index 00000000..f215686d --- /dev/null +++ b/dnsapi/dns_azion.sh @@ -0,0 +1,204 @@ +#!/usr/bin/env sh + +# +#AZION_Email="" +#AZION_Password="" +# + +AZION_Api="https://api.azionapi.net" + +######## Public functions ######## + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to add txt record +dns_azion_add() { + fulldomain=$1 + txtvalue=$2 + + _debug "Detect the root zone" + if ! _get_root "$fulldomain"; then + _err "Domain not found" + return 1 + fi + + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + _debug _domain_id "$_domain_id" + + _info "Add or update record" + _get_record "$_domain_id" "$_sub_domain" + if [ "$record_id" ]; then + _payload="{\"record_type\": \"TXT\", \"entry\": \"$_sub_domain\", \"answers_list\": [$answers_list, \"$txtvalue\"], \"ttl\": 20}" + if _azion_rest PUT "intelligent_dns/$_domain_id/records/$record_id" "$_payload"; then + if _contains "$response" "$txtvalue"; then + _info "Record updated." + return 0 + fi + fi + else + _payload="{\"record_type\": \"TXT\", \"entry\": \"$_sub_domain\", \"answers_list\": [\"$txtvalue\"], \"ttl\": 20}" + if _azion_rest POST "intelligent_dns/$_domain_id/records" "$_payload"; then + if _contains "$response" "$txtvalue"; then + _info "Record added." + return 0 + fi + fi + fi + _err "Failed to add or update record." + return 1 +} + +# Usage: fulldomain txtvalue +# Used to remove the txt record after validation +dns_azion_rm() { + fulldomain=$1 + txtvalue=$2 + + _debug "Detect the root zone" + if ! _get_root "$fulldomain"; then + _err "Domain not found" + return 1 + fi + + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + _debug _domain_id "$_domain_id" + + _info "Removing record" + _get_record "$_domain_id" "$_sub_domain" + if [ "$record_id" ]; then + if _azion_rest DELETE "intelligent_dns/$_domain_id/records/$record_id"; then + _info "Record removed." + return 0 + else + _err "Failed to remove record." + return 1 + fi + else + _info "Record not found or already removed." + return 0 + fi +} + +#################### Private functions below ################################## +# Usage: _acme-challenge.www.domain.com +# returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg +_get_root() { + domain=$1 + i=1 + p=1 + + if ! _azion_rest GET "intelligent_dns"; then + return 1 + fi + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + # not valid + return 1 + fi + + if _contains "$response" "\"domain\":\"$h\""; then + _domain_id=$(echo "$response" | tr '{' "\n" | grep "\"domain\":\"$h\"" | _egrep_o "\"id\":[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d \") + _debug _domain_id "$_domain_id" + if [ "$_domain_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 + return 1 +} + +_get_record() { + _domain_id=$1 + _record=$2 + + if ! _azion_rest GET "intelligent_dns/$_domain_id/records"; then + return 1 + fi + + if _contains "$response" "\"entry\":\"$_record\""; then + _json_record=$(echo "$response" | tr '{' "\n" | grep "\"entry\":\"$_record\"") + if [ "$_json_record" ]; then + record_id=$(echo "$_json_record" | _egrep_o "\"record_id\":[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d \") + answers_list=$(echo "$_json_record" | _egrep_o "\"answers_list\":\[.*\]" | _head_n 1 | cut -d : -f 2 | tr -d \[\]) + return 0 + fi + return 1 + fi + return 1 +} + +_get_token() { + AZION_Email="${AZION_Email:-$(_readaccountconf_mutable AZION_Email)}" + AZION_Password="${AZION_Password:-$(_readaccountconf_mutable AZION_Password)}" + + if ! _contains "$AZION_Email" "@"; then + _err "It seems that the AZION_Email is not a valid email address. Revalidate your environments." + return 1 + fi + + if [ -z "$AZION_Email" ] || [ -z "$AZION_Password" ]; then + _err "You didn't specified a AZION_Email/AZION_Password to generate Azion token." + return 1 + fi + + _saveaccountconf_mutable AZION_Email "$AZION_Email" + _saveaccountconf_mutable AZION_Password "$AZION_Password" + + _basic_auth=$(printf "%s:%s" "$AZION_Email" "$AZION_Password" | _base64) + _debug _basic_auth "$_basic_auth" + + export _H1="Accept: application/json; version=3" + export _H2="Content-Type: application/json" + export _H3="Authorization: Basic $_basic_auth" + + response="$(_post "" "$AZION_Api/tokens" "" "POST")" + if _contains "$response" "\"token\":\"" >/dev/null; then + _azion_token=$(echo "$response" | _egrep_o "\"token\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") + export AZION_Token="$_azion_token" + else + _err "Failed to generate Azion token" + return 1 + fi +} + +_azion_rest() { + _method=$1 + _uri="$2" + _data="$3" + + if [ -z "$AZION_Token" ]; then + _get_token + fi + _debug2 token "$AZION_Token" + + export _H1="Accept: application/json; version=3" + export _H2="Content-Type: application/json" + export _H3="Authorization: token $AZION_Token" + + if [ "$_method" != "GET" ]; then + _debug _data "$_data" + response="$(_post "$_data" "$AZION_Api/$_uri" "" "$_method")" + else + response="$(_get "$AZION_Api/$_uri")" + fi + + _debug2 response "$response" + + if [ "$?" != "0" ]; then + _err "error $_method $_uri $_data" + return 1 + fi + return 0 +} diff --git a/dnsapi/dns_pdns.sh b/dnsapi/dns_pdns.sh index 28b35492..6aa2e953 100755 --- a/dnsapi/dns_pdns.sh +++ b/dnsapi/dns_pdns.sh @@ -103,7 +103,7 @@ set_record() { _build_record_string "$oldchallenge" done - if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then + if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}" "application/json"; then _err "Set txt record error." return 1 fi @@ -126,7 +126,7 @@ rm_record() { if _contains "$_existing_challenges" "$txtvalue"; then #Delete all challenges (PowerDNS API does not allow to delete content) - if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then + if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}" "application/json"; then _err "Delete txt record error." return 1 fi @@ -140,7 +140,7 @@ rm_record() { fi done #Recreate the existing challenges - if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then + if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}" "application/json"; then _err "Set txt record error." return 1 fi @@ -203,12 +203,13 @@ _pdns_rest() { method=$1 ep=$2 data=$3 + ct=$4 export _H1="X-API-Key: $PDNS_Token" if [ ! "$method" = "GET" ]; then _debug data "$data" - response="$(_post "$data" "$PDNS_Url$ep" "" "$method")" + response="$(_post "$data" "$PDNS_Url$ep" "" "$method" "$ct")" else response="$(_get "$PDNS_Url$ep")" fi diff --git a/notify/telegram.sh b/notify/telegram.sh index d16f3a98..454b4146 100644 --- a/notify/telegram.sh +++ b/notify/telegram.sh @@ -27,7 +27,7 @@ telegram_send() { fi _saveaccountconf_mutable TELEGRAM_BOT_CHATID "$TELEGRAM_BOT_CHATID" - _content="$(printf "%s" "$_content" | sed -e 's/*/\\\\*/')" + _content="$(printf "%s" "$_content" | sed -e 's/\([_*`\[]\)/\\\\\1/g')" _content="$(printf "*%s*\n%s" "$_subject" "$_content" | _json_encode)" _data="{\"text\": \"$_content\", " _data="$_data\"chat_id\": \"$TELEGRAM_BOT_CHATID\", "