diff --git a/Dockerfile b/Dockerfile index 4d7d22b1..7523f0af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.17 +FROM alpine:3.21 RUN apk --no-cache add -f \ openssl \ diff --git a/deploy/vault.sh b/deploy/vault.sh index 03a0de83..89994f4b 100644 --- a/deploy/vault.sh +++ b/deploy/vault.sh @@ -80,10 +80,15 @@ vault_deploy() { if [ -n "$VAULT_RENEW_TOKEN" ]; then URL="$VAULT_ADDR/v1/auth/token/renew-self" _info "Renew the Vault token to default TTL" - if ! _post "" "$URL" >/dev/null; then + _response=$(_post "" "$URL") + if [ "$?" != "0" ]; then _err "Failed to renew the Vault token" return 1 fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Failed to renew the Vault token: $_response" + return 1 + fi fi URL="$VAULT_ADDR/v1/$VAULT_PREFIX/$_cdomain" @@ -91,29 +96,85 @@ vault_deploy() { if [ -n "$VAULT_FABIO_MODE" ]; then _info "Writing certificate and key to $URL in Fabio mode" if [ -n "$VAULT_KV_V2" ]; then - _post "{ \"data\": {\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"} }" "$URL" >/dev/null || return 1 + _response=$(_post "{ \"data\": {\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"} }" "$URL") + if [ "$?" != "0" ]; then return 1; fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Vault error: $_response" + return 1 + fi else - _post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL" >/dev/null || return 1 + _response=$(_post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL") + if [ "$?" != "0" ]; then return 1; fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Vault error: $_response" + return 1 + fi fi else if [ -n "$VAULT_KV_V2" ]; then _info "Writing certificate to $URL/cert.pem" - _post "{\"data\": {\"value\": \"$_ccert\"}}" "$URL/cert.pem" >/dev/null || return 1 + _response=$(_post "{\"data\": {\"value\": \"$_ccert\"}}" "$URL/cert.pem") + if [ "$?" != "0" ]; then return 1; fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Vault error writing cert.pem: $_response" + return 1 + fi + _info "Writing key to $URL/cert.key" - _post "{\"data\": {\"value\": \"$_ckey\"}}" "$URL/cert.key" >/dev/null || return 1 + _response=$(_post "{\"data\": {\"value\": \"$_ckey\"}}" "$URL/cert.key") + if [ "$?" != "0" ]; then return 1; fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Vault error writing cert.key: $_response" + return 1 + fi + _info "Writing CA certificate to $URL/ca.pem" - _post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/ca.pem" >/dev/null || return 1 + _response=$(_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/ca.pem") + if [ "$?" != "0" ]; then return 1; fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Vault error writing ca.pem: $_response" + return 1 + fi + _info "Writing full-chain certificate to $URL/fullchain.pem" - _post "{\"data\": {\"value\": \"$_cfullchain\"}}" "$URL/fullchain.pem" >/dev/null || return 1 + _response=$(_post "{\"data\": {\"value\": \"$_cfullchain\"}}" "$URL/fullchain.pem") + if [ "$?" != "0" ]; then return 1; fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Vault error writing fullchain.pem: $_response" + return 1 + fi else _info "Writing certificate to $URL/cert.pem" - _post "{\"value\": \"$_ccert\"}" "$URL/cert.pem" >/dev/null || return 1 + _response=$(_post "{\"value\": \"$_ccert\"}" "$URL/cert.pem") + if [ "$?" != "0" ]; then return 1; fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Vault error writing cert.pem: $_response" + return 1 + fi + _info "Writing key to $URL/cert.key" - _post "{\"value\": \"$_ckey\"}" "$URL/cert.key" >/dev/null || return 1 + _response=$(_post "{\"value\": \"$_ckey\"}" "$URL/cert.key") + if [ "$?" != "0" ]; then return 1; fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Vault error writing cert.key: $_response" + return 1 + fi + _info "Writing CA certificate to $URL/ca.pem" - _post "{\"value\": \"$_cca\"}" "$URL/ca.pem" >/dev/null || return 1 + _response=$(_post "{\"value\": \"$_cca\"}" "$URL/ca.pem") + if [ "$?" != "0" ]; then return 1; fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Vault error writing ca.pem: $_response" + return 1 + fi + _info "Writing full-chain certificate to $URL/fullchain.pem" - _post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem" >/dev/null || return 1 + _response=$(_post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem") + if [ "$?" != "0" ]; then return 1; fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Vault error writing fullchain.pem: $_response" + return 1 + fi fi # To make it compatible with the wrong ca path `chain.pem` which was used in former versions @@ -121,11 +182,20 @@ vault_deploy() { _err "The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning" _info "Updating CA certificate to $URL/chain.pem for backward compatibility" if [ -n "$VAULT_KV_V2" ]; then - _post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/chain.pem" >/dev/null || return 1 + _response=$(_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/chain.pem") + if [ "$?" != "0" ]; then return 1; fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Vault error writing chain.pem: $_response" + return 1 + fi else - _post "{\"value\": \"$_cca\"}" "$URL/chain.pem" >/dev/null || return 1 + _response=$(_post "{\"value\": \"$_cca\"}" "$URL/chain.pem") + if [ "$?" != "0" ]; then return 1; fi + if echo "$_response" | grep -q '"errors":\['; then + _err "Vault error writing chain.pem: $_response" + return 1 + fi fi fi fi - } diff --git a/dnsapi/dns_edgecenter.sh b/dnsapi/dns_edgecenter.sh new file mode 100644 index 00000000..cdd150df --- /dev/null +++ b/dnsapi/dns_edgecenter.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 + +# EdgeCenter DNS API integration for acme.sh +# Author: Konstantin Ruchev +dns_edgecenter_info='edgecenter DNS API +Site: https://edgecenter.ru +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_edgecenter +Options: + EDGECENTER_API_KEY auth APIKey' + +EDGECENTER_API="https://api.edgecenter.ru" +DOMAIN_TYPE= +DOMAIN_MASTER= + +######## Public functions ##################### + +#Usage: dns_edgecenter_add _acme-challenge.www.domain.com "TXT_RECORD_VALUE" +dns_edgecenter_add() { + fulldomain="$1" + txtvalue="$2" + + _info "Using EdgeCenter DNS API" + + if ! _dns_edgecenter_init_check; then + return 1 + fi + + _debug "Detecting root zone for $fulldomain" + if ! _get_root "$fulldomain"; then + return 1 + fi + + subdomain="${fulldomain%."$_zone"}" + subdomain=${subdomain%.} + + _debug "Zone: $_zone" + _debug "Subdomain: $subdomain" + _debug "TXT value: $txtvalue" + + payload='{"resource_records": [ { "content": ["'"$txtvalue"'"] } ], "ttl": 60 }' + _dns_edgecenter_http_api_call "post" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" "$payload" + + if _contains "$response" '"error":"rrset is already exists"'; then + _debug "RRSet exists, merging values" + _dns_edgecenter_http_api_call "get" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" + current="$response" + newlist="" + for v in $(echo "$current" | sed -n 's/.*"content":\["\([^"]*\)"\].*/\1/p'); do + newlist="$newlist {\"content\":[\"$v\"]}," + done + newlist="$newlist{\"content\":[\"$txtvalue\"]}" + putdata="{\"resource_records\":[${newlist}]} +" + _dns_edgecenter_http_api_call "put" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" "$putdata" + _info "Updated existing RRSet with new TXT value." + return 0 + fi + + if _contains "$response" '"exception":'; then + _err "Record cannot be added." + return 1 + fi + + _info "TXT record added successfully." + return 0 +} + +#Usage: dns_edgecenter_rm _acme-challenge.www.domain.com "TXT_RECORD_VALUE" +dns_edgecenter_rm() { + fulldomain="$1" + txtvalue="$2" + + _info "Removing TXT record for $fulldomain" + + if ! _dns_edgecenter_init_check; then + return 1 + fi + + if ! _get_root "$fulldomain"; then + return 1 + fi + + subdomain="${fulldomain%."$_zone"}" + subdomain=${subdomain%.} + + _dns_edgecenter_http_api_call "delete" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" + + if [ -z "$response" ]; then + _info "TXT record deleted successfully." + else + _info "TXT record may not have been deleted: $response" + fi + return 0 +} + +#################### Private functions below ################################## + +_dns_edgecenter_init_check() { + EDGECENTER_API_KEY="${EDGECENTER_API_KEY:-$(_readaccountconf_mutable EDGECENTER_API_KEY)}" + if [ -z "$EDGECENTER_API_KEY" ]; then + _err "EDGECENTER_API_KEY was not exported." + return 1 + fi + + _saveaccountconf_mutable EDGECENTER_API_KEY "$EDGECENTER_API_KEY" + export _H1="Authorization: APIKey $EDGECENTER_API_KEY" + + _dns_edgecenter_http_api_call "get" "dns/v2/clients/me/features" + if ! _contains "$response" '"id":'; then + _err "Invalid API key." + return 1 + fi + return 0 +} + +_get_root() { + domain="$1" + i=1 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f "$i"-) + if [ -z "$h" ]; then + return 1 + fi + _dns_edgecenter_http_api_call "get" "dns/v2/zones/$h" + if ! _contains "$response" 'zone is not found'; then + _zone="$h" + return 0 + fi + i=$((i + 1)) + done + return 1 +} + +_dns_edgecenter_http_api_call() { + mtd="$1" + endpoint="$2" + data="$3" + + export _H1="Authorization: APIKey $EDGECENTER_API_KEY" + + case "$mtd" in + get) + response="$(_get "$EDGECENTER_API/$endpoint")" + ;; + post) + response="$(_post "$data" "$EDGECENTER_API/$endpoint")" + ;; + delete) + response="$(_post "" "$EDGECENTER_API/$endpoint" "" "DELETE")" + ;; + put) + response="$(_post "$data" "$EDGECENTER_API/$endpoint" "" "PUT")" + ;; + *) + _err "Unknown HTTP method $mtd" + return 1 + ;; + esac + + _debug "HTTP $mtd response: $response" + return 0 +}