You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

271 lines
8.5 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. #!/usr/bin/env sh
  2. # shellcheck disable=SC2034
  3. dns_yc_info='Yandex Cloud DNS
  4. Site: Cloud.Yandex.com
  5. Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yc
  6. Options:
  7. YC_Zone_ID DNS Zone ID
  8. YC_Folder_ID YC Folder ID
  9. YC_SA_ID Service Account ID
  10. YC_SA_Key_ID Service Account IAM Key ID
  11. YC_SA_Key_File_Path Private key file path. Optional.
  12. YC_SA_Key_File_PEM_b64 Base64 content of private key file. Use instead of Path to private key file. Optional.
  13. Issues: github.com/acmesh-official/acme.sh/issues/4210
  14. '
  15. YC_Api="https://dns.api.cloud.yandex.net/dns/v1"
  16. ######## Public functions #####################
  17. #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
  18. dns_yc_add() {
  19. fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
  20. txtvalue=$2
  21. YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readaccountconf_mutable YC_SA_Key_File_PEM_b64)}"
  22. YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readaccountconf_mutable YC_SA_Key_File_Path)}"
  23. if [ "$YC_SA_Key_File_PEM_b64" ]; then
  24. echo "$YC_SA_Key_File_PEM_b64" | _dbase64 >private.key
  25. YC_SA_Key_File="private.key"
  26. _savedomainconf YC_SA_Key_File_PEM_b64 "$YC_SA_Key_File_PEM_b64"
  27. else
  28. YC_SA_Key_File="$YC_SA_Key_File_Path"
  29. _savedomainconf YC_SA_Key_File_Path "$YC_SA_Key_File_Path"
  30. fi
  31. YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
  32. YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
  33. YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
  34. YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
  35. if [ "$YC_SA_ID" ] && [ "$YC_SA_Key_ID" ] && [ "$YC_SA_Key_File" ]; then
  36. if [ -f "$YC_SA_Key_File" ]; then
  37. if _isRSA "$YC_SA_Key_File" >/dev/null 2>&1; then
  38. if [ "$YC_Zone_ID" ]; then
  39. _savedomainconf YC_Zone_ID "$YC_Zone_ID"
  40. _savedomainconf YC_SA_ID "$YC_SA_ID"
  41. _savedomainconf YC_SA_Key_ID "$YC_SA_Key_ID"
  42. elif [ "$YC_Folder_ID" ]; then
  43. _savedomainconf YC_Folder_ID "$YC_Folder_ID"
  44. _saveaccountconf_mutable YC_SA_ID "$YC_SA_ID"
  45. _saveaccountconf_mutable YC_SA_Key_ID "$YC_SA_Key_ID"
  46. _clearaccountconf_mutable YC_Zone_ID
  47. _clearaccountconf YC_Zone_ID
  48. else
  49. _err "You didn't specify a Yandex Cloud Zone ID or Folder ID yet."
  50. return 1
  51. fi
  52. else
  53. _err "YC_SA_Key_File not a RSA file(_isRSA function return false)."
  54. return 1
  55. fi
  56. else
  57. _err "YC_SA_Key_File not found in path $YC_SA_Key_File."
  58. return 1
  59. fi
  60. else
  61. _clearaccountconf YC_Zone_ID
  62. _clearaccountconf YC_Folder_ID
  63. _clearaccountconf YC_SA_ID
  64. _clearaccountconf YC_SA_Key_ID
  65. _clearaccountconf YC_SA_Key_File_PEM_b64
  66. _clearaccountconf YC_SA_Key_File_Path
  67. _err "You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File."
  68. return 1
  69. fi
  70. _debug "First detect the root zone"
  71. if ! _get_root "$fulldomain"; then
  72. _err "invalid domain"
  73. return 1
  74. fi
  75. _debug _domain_id "$_domain_id"
  76. _debug _sub_domain "$_sub_domain"
  77. _debug _domain "$_domain"
  78. _debug "Getting txt records"
  79. if ! _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then
  80. _err "Error: $response"
  81. return 1
  82. fi
  83. _info "Adding record"
  84. if _yc_rest POST "zones/$_domain_id:upsertRecordSets" "{\"merges\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":[\"$txtvalue\"]}]}"; then
  85. if _contains "$response" "\"done\": true"; then
  86. _info "Added, OK"
  87. return 0
  88. else
  89. _err "Add txt record error."
  90. return 1
  91. fi
  92. fi
  93. _err "Add txt record error."
  94. return 1
  95. }
  96. #fulldomain txtvalue
  97. dns_yc_rm() {
  98. fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
  99. txtvalue=$2
  100. YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
  101. YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
  102. YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
  103. YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
  104. _debug "First detect the root zone"
  105. if ! _get_root "$fulldomain"; then
  106. _err "invalid domain"
  107. return 1
  108. fi
  109. _debug _domain_id "$_domain_id"
  110. _debug _sub_domain "$_sub_domain"
  111. _debug _domain "$_domain"
  112. _debug "Getting txt records"
  113. if _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then
  114. exists_txtvalue=$(echo "$response" | _normalizeJson | _egrep_o "\"data\".*\][^,]*" | _egrep_o "[^:]*$")
  115. _debug exists_txtvalue "$exists_txtvalue"
  116. else
  117. _err "Error: $response"
  118. return 1
  119. fi
  120. if _yc_rest POST "zones/$_domain_id:updateRecordSets" "{\"deletions\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":$exists_txtvalue}]}"; then
  121. if _contains "$response" "\"done\": true"; then
  122. _info "Delete, OK"
  123. return 0
  124. else
  125. _err "Delete record error."
  126. return 1
  127. fi
  128. fi
  129. _err "Delete record error."
  130. return 1
  131. }
  132. #################### Private functions below ##################################
  133. #_acme-challenge.www.domain.com
  134. #returns
  135. # _sub_domain=_acme-challenge.www
  136. # _domain=domain.com
  137. # _domain_id=sdjkglgdfewsdfg
  138. _get_root() {
  139. domain=$1
  140. i=1
  141. p=1
  142. # Use Zone ID directly if provided
  143. if [ "$YC_Zone_ID" ]; then
  144. if ! _yc_rest GET "zones/$YC_Zone_ID"; then
  145. return 1
  146. else
  147. if echo "$response" | tr -d " " | _egrep_o "\"id\":\"$YC_Zone_ID\"" >/dev/null; then
  148. _domain=$(echo "$response" | _egrep_o "\"zone\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
  149. if [ "$_domain" ]; then
  150. _cutlength=$((${#domain} - ${#_domain}))
  151. _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cutlength")
  152. _domain_id=$YC_Zone_ID
  153. return 0
  154. else
  155. return 1
  156. fi
  157. else
  158. return 1
  159. fi
  160. fi
  161. fi
  162. while true; do
  163. h=$(printf "%s" "$domain" | cut -d . -f $i-100)
  164. _debug h "$h"
  165. if [ -z "$h" ]; then
  166. #not valid
  167. return 1
  168. fi
  169. if [ "$YC_Folder_ID" ]; then
  170. if ! _yc_rest GET "zones?folderId=$YC_Folder_ID"; then
  171. return 1
  172. fi
  173. else
  174. echo "You didn't specify a Yandex Cloud Folder ID."
  175. return 1
  176. fi
  177. if _contains "$response" "\"zone\": \"$h\""; then
  178. _domain_id=$(echo "$response" | _normalizeJson | _egrep_o "[^{]*\"zone\":\"$h\"[^}]*" | _egrep_o "\"id\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')
  179. _debug _domain_id "$_domain_id"
  180. if [ "$_domain_id" ]; then
  181. _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
  182. _domain=$h
  183. return 0
  184. fi
  185. return 1
  186. fi
  187. p=$i
  188. i=$(_math "$i" + 1)
  189. done
  190. return 1
  191. }
  192. _yc_rest() {
  193. m=$1
  194. ep="$2"
  195. data="$3"
  196. _debug "$ep"
  197. if [ ! "$YC_Token" ]; then
  198. _debug "Login"
  199. _yc_login
  200. else
  201. _debug "Token already exists. Skip Login."
  202. fi
  203. token_trimmed=$(echo "$YC_Token" | tr -d '"')
  204. export _H1="Content-Type: application/json"
  205. export _H2="Authorization: Bearer $token_trimmed"
  206. if [ "$m" != "GET" ]; then
  207. _debug data "$data"
  208. response="$(_post "$data" "$YC_Api/$ep" "" "$m")"
  209. else
  210. response="$(_get "$YC_Api/$ep")"
  211. fi
  212. if [ "$?" != "0" ]; then
  213. _err "error $ep"
  214. return 1
  215. fi
  216. _debug2 response "$response"
  217. return 0
  218. }
  219. _yc_login() {
  220. header=$(echo "{\"typ\":\"JWT\",\"alg\":\"PS256\",\"kid\":\"$YC_SA_Key_ID\"}" | _normalizeJson | _base64 | _url_replace)
  221. _debug header "$header"
  222. _current_timestamp=$(_time)
  223. _expire_timestamp=$(_math "$_current_timestamp" + 1200) # 20 minutes
  224. payload=$(echo "{\"iss\":\"$YC_SA_ID\",\"aud\":\"https://iam.api.cloud.yandex.net/iam/v1/tokens\",\"iat\":$_current_timestamp,\"exp\":$_expire_timestamp}" | _normalizeJson | _base64 | _url_replace)
  225. _debug payload "$payload"
  226. #signature=$(printf "%s.%s" "$header" "$payload" | ${ACME_OPENSSL_BIN:-openssl} dgst -sign "$YC_SA_Key_File -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1" | _base64 | _url_replace )
  227. _signature=$(printf "%s.%s" "$header" "$payload" | _sign "$YC_SA_Key_File" "sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1" | _url_replace)
  228. _debug2 _signature "$_signature"
  229. rm -rf "$YC_SA_Key_File"
  230. _jwt=$(printf "{\"jwt\": \"%s.%s.%s\"}" "$header" "$payload" "$_signature")
  231. _debug2 _jwt "$_jwt"
  232. export _H1="Content-Type: application/json"
  233. _iam_response="$(_post "$_jwt" "https://iam.api.cloud.yandex.net/iam/v1/tokens" "" "POST")"
  234. _debug3 _iam_response "$(echo "$_iam_response" | _normalizeJson)"
  235. YC_Token="$(echo "$_iam_response" | _normalizeJson | _egrep_o "\"iamToken\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')"
  236. _debug3 YC_Token
  237. return 0
  238. }