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.

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