dns_selectel.sh 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. #!/usr/bin/env sh
  2. # shellcheck disable=SC2034
  3. dns_selectel_info='Selectel.com
  4. Domains: Selectel.ru
  5. Site: Selectel.com
  6. Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel
  7. Options: For old API version v1 (deprecated)
  8. SL_Ver API version. Use "v1".
  9. SL_Key API Key
  10. OptionsAlt: For the current API version v2
  11. SL_Ver API version. Use "v2".
  12. SL_Login_ID Account ID
  13. SL_Project_Name Project name
  14. SL_Login_Name Service user name
  15. SL_Pswd Service user password
  16. SL_Expire Token lifetime. In minutes (0-1440). Default "1400"
  17. Issues: github.com/acmesh-official/acme.sh/issues/5126
  18. '
  19. SL_Api="https://api.selectel.ru/domains"
  20. auth_uri="https://cloud.api.selcloud.ru/identity/v3/auth/tokens"
  21. _sl_sep='#'
  22. ######## Public functions #####################
  23. #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
  24. dns_selectel_add() {
  25. fulldomain=$1
  26. txtvalue=$2
  27. if ! _sl_init_vars; then
  28. return 1
  29. fi
  30. _debug2 SL_Ver "$SL_Ver"
  31. _debug2 SL_Expire "$SL_Expire"
  32. _debug2 SL_Login_Name "$SL_Login_Name"
  33. _debug2 SL_Login_ID "$SL_Login_ID"
  34. _debug2 SL_Project_Name "$SL_Project_Name"
  35. _debug "First detect the root zone"
  36. if ! _get_root "$fulldomain"; then
  37. _err "invalid domain"
  38. return 1
  39. fi
  40. _debug _domain_id "$_domain_id"
  41. _debug _sub_domain "$_sub_domain"
  42. _debug _domain "$_domain"
  43. _info "Adding record"
  44. if [ "$SL_Ver" = "v2" ]; then
  45. _ext_srv1="/zones/"
  46. _ext_srv2="/rrset/"
  47. _text_tmp=$(echo "$txtvalue" | sed -En "s/[\"]*([^\"]*)/\1/p")
  48. _text_tmp='\"'$_text_tmp'\"'
  49. _data="{\"type\": \"TXT\", \"ttl\": 60, \"name\": \"${fulldomain}.\", \"records\": [{\"content\":\"$_text_tmp\"}]}"
  50. elif [ "$SL_Ver" = "v1" ]; then
  51. _ext_srv1="/"
  52. _ext_srv2="/records/"
  53. _data="{\"type\":\"TXT\",\"ttl\":60,\"name\":\"$fulldomain\",\"content\":\"$txtvalue\"}"
  54. else
  55. _err "Error. Unsupported version API $SL_Ver"
  56. return 1
  57. fi
  58. _ext_uri="${_ext_srv1}$_domain_id${_ext_srv2}"
  59. _debug _ext_uri "$_ext_uri"
  60. _debug _data "$_data"
  61. if _sl_rest POST "$_ext_uri" "$_data"; then
  62. if _contains "$response" "$txtvalue"; then
  63. _info "Added, OK"
  64. return 0
  65. fi
  66. if _contains "$response" "already_exists"; then
  67. # record TXT with $fulldomain already exists
  68. if [ "$SL_Ver" = "v2" ]; then
  69. # It is necessary to add one more content to the comments
  70. # read all records rrset
  71. _debug "Getting txt records"
  72. _sl_rest GET "${_ext_uri}"
  73. # There is already a $txtvalue value, no need to add it
  74. if _contains "$response" "$txtvalue"; then
  75. _info "Added, OK"
  76. _info "Txt record ${fulldomain} with value ${txtvalue} already exists"
  77. return 0
  78. fi
  79. # group \1 - full record rrset; group \2 - records attribute value, exactly {"content":"\"value1\""},{"content":"\"value2\""}",...
  80. _record_seg="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*${fulldomain}[^}]*records[^}]*\[(\{[^]]*\})\][^}]*}).*/\1/p")"
  81. _record_array="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*${fulldomain}[^}]*records[^}]*\[(\{[^]]*\})\][^}]*}).*/\2/p")"
  82. # record id
  83. _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"")"
  84. # preparing _data
  85. _tmp_str="${_record_array},{\"content\":\"${_text_tmp}\"}"
  86. _data="{\"ttl\": 60, \"records\": [${_tmp_str}]}"
  87. _debug2 _record_seg "$_record_seg"
  88. _debug2 _record_array "$_record_array"
  89. _debug2 _record_array "$_record_id"
  90. _debug "New data for record" "$_data"
  91. if _sl_rest PATCH "${_ext_uri}${_record_id}" "$_data"; then
  92. _info "Added, OK"
  93. return 0
  94. fi
  95. elif [ "$SL_Ver" = "v1" ]; then
  96. _info "Added, OK"
  97. return 0
  98. fi
  99. fi
  100. fi
  101. _err "Add txt record error."
  102. return 1
  103. }
  104. #fulldomain txtvalue
  105. dns_selectel_rm() {
  106. fulldomain=$1
  107. txtvalue=$2
  108. if ! _sl_init_vars "nosave"; then
  109. return 1
  110. fi
  111. _debug2 SL_Ver "$SL_Ver"
  112. _debug2 SL_Expire "$SL_Expire"
  113. _debug2 SL_Login_Name "$SL_Login_Name"
  114. _debug2 SL_Login_ID "$SL_Login_ID"
  115. _debug2 SL_Project_Name "$SL_Project_Name"
  116. #
  117. _debug "First detect the root zone"
  118. if ! _get_root "$fulldomain"; then
  119. _err "invalid domain"
  120. return 1
  121. fi
  122. _debug _domain_id "$_domain_id"
  123. _debug _sub_domain "$_sub_domain"
  124. _debug _domain "$_domain"
  125. #
  126. if [ "$SL_Ver" = "v2" ]; then
  127. _ext_srv1="/zones/"
  128. _ext_srv2="/rrset/"
  129. elif [ "$SL_Ver" = "v1" ]; then
  130. _ext_srv1="/"
  131. _ext_srv2="/records/"
  132. else
  133. _err "Error. Unsupported version API $SL_Ver"
  134. return 1
  135. fi
  136. #
  137. _debug "Getting txt records"
  138. _ext_uri="${_ext_srv1}$_domain_id${_ext_srv2}"
  139. _debug _ext_uri "$_ext_uri"
  140. _sl_rest GET "${_ext_uri}"
  141. #
  142. if ! _contains "$response" "$txtvalue"; then
  143. _err "Txt record not found"
  144. return 1
  145. fi
  146. #
  147. if [ "$SL_Ver" = "v2" ]; then
  148. _record_seg="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\1/gp")"
  149. _record_arr="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\3/p")"
  150. elif [ "$SL_Ver" = "v1" ]; then
  151. _record_seg="$(echo "$response" | _egrep_o "[^{]*\"content\" *: *\"$txtvalue\"[^}]*}")"
  152. else
  153. _err "Error. Unsupported version API $SL_Ver"
  154. return 1
  155. fi
  156. _debug2 "_record_seg" "$_record_seg"
  157. if [ -z "$_record_seg" ]; then
  158. _err "can not find _record_seg"
  159. return 1
  160. fi
  161. # record id
  162. # the following lines change the algorithm for deleting records with the value $txtvalue
  163. # if you use the 1st line, then all such records are deleted at once
  164. # if you use the 2nd line, then only the first entry from them is deleted
  165. #_record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"")"
  166. _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"" | sed '1!d')"
  167. if [ -z "$_record_id" ]; then
  168. _err "can not find _record_id"
  169. return 1
  170. fi
  171. _debug2 "_record_id" "$_record_id"
  172. # delete all record type TXT with text $txtvalue
  173. if [ "$SL_Ver" = "v2" ]; then
  174. # actual
  175. _new_arr="$(echo "$_record_seg" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\3/gp" | sed -En "s/(\},\{)/}\n{/gp" | sed "/${txtvalue}/d" | sed ":a;N;s/\n/,/;ta")"
  176. # uri record for DEL or PATCH
  177. _del_uri="${_ext_uri}${_record_id}"
  178. _debug _del_uri "$_del_uri"
  179. if [ -z "$_new_arr" ]; then
  180. # remove record
  181. if ! _sl_rest DELETE "${_del_uri}"; then
  182. _err "Delete record error: ${_del_uri}."
  183. else
  184. info "Delete record success: ${_del_uri}."
  185. fi
  186. else
  187. # update a record by removing one element in content
  188. _data="{\"ttl\": 60, \"records\": [${_new_arr}]}"
  189. _debug2 _data "$_data"
  190. # REST API PATCH call
  191. if _sl_rest PATCH "${_del_uri}" "$_data"; then
  192. _info "Patched, OK: ${_del_uri}"
  193. else
  194. _err "Patched record error: ${_del_uri}."
  195. fi
  196. fi
  197. else
  198. # legacy
  199. for _one_id in $_record_id; do
  200. _del_uri="${_ext_uri}${_one_id}"
  201. _debug _del_uri "$_del_uri"
  202. if ! _sl_rest DELETE "${_del_uri}"; then
  203. _err "Delete record error: ${_del_uri}."
  204. else
  205. info "Delete record success: ${_del_uri}."
  206. fi
  207. done
  208. fi
  209. return 0
  210. }
  211. #################### Private functions below ##################################
  212. _get_root() {
  213. domain=$1
  214. if [ "$SL_Ver" = 'v1' ]; then
  215. # version API 1
  216. if ! _sl_rest GET "/"; then
  217. return 1
  218. fi
  219. i=2
  220. p=1
  221. while true; do
  222. h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
  223. _debug h "$h"
  224. if [ -z "$h" ]; then
  225. return 1
  226. fi
  227. if _contains "$response" "\"name\" *: *\"$h\","; then
  228. _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
  229. _domain=$h
  230. _debug "Getting domain id for $h"
  231. if ! _sl_rest GET "/$h"; then
  232. _err "Error read records of all domains $SL_Ver"
  233. return 1
  234. fi
  235. _domain_id="$(echo "$response" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\":" | cut -d : -f 2)"
  236. return 0
  237. fi
  238. p=$i
  239. i=$(_math "$i" + 1)
  240. done
  241. _err "Error read records of all domains $SL_Ver"
  242. return 1
  243. elif [ "$SL_Ver" = "v2" ]; then
  244. # version API 2
  245. _ext_uri='/zones/'
  246. domain="${domain}."
  247. _debug "domain:: " "$domain"
  248. # read records of all domains
  249. if ! _sl_rest GET "$_ext_uri"; then
  250. _err "Error read records of all domains $SL_Ver"
  251. return 1
  252. fi
  253. i=1
  254. p=1
  255. while true; do
  256. h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
  257. _debug h "$h"
  258. if [ -z "$h" ]; then
  259. _err "The domain was not found among the registered ones"
  260. return 1
  261. fi
  262. _domain_record=$(echo "$response" | sed -En "s/.*(\{[^}]*id[^}]*\"name\" *: *\"$h\"[^}]*}).*/\1/p")
  263. _debug "_domain_record:: " "$_domain_record"
  264. if [ -n "$_domain_record" ]; then
  265. _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
  266. _domain=$h
  267. _debug "Getting domain id for $h"
  268. _domain_id=$(echo "$_domain_record" | sed -En "s/\{[^}]*\"id\" *: *\"([^\"]*)\"[^}]*\}/\1/p")
  269. return 0
  270. fi
  271. p=$i
  272. i=$(_math "$i" + 1)
  273. done
  274. _err "Error read records of all domains $SL_Ver"
  275. return 1
  276. else
  277. _err "Error. Unsupported version API $SL_Ver"
  278. return 1
  279. fi
  280. }
  281. #################################################################
  282. # use: method add_url body
  283. _sl_rest() {
  284. m=$1
  285. ep="$2"
  286. data="$3"
  287. _token=$(_get_auth_token)
  288. if [ -z "$_token" ]; then
  289. _err "BAD key or token $ep"
  290. return 1
  291. fi
  292. if [ "$SL_Ver" = v2 ]; then
  293. _h1_name="X-Auth-Token"
  294. else
  295. _h1_name='X-Token'
  296. fi
  297. export _H1="${_h1_name}: ${_token}"
  298. export _H2="Content-Type: application/json"
  299. _debug2 "Full URI: " "$SL_Api/${SL_Ver}${ep}"
  300. _debug2 "_H1:" "$_H1"
  301. _debug2 "_H2:" "$_H2"
  302. if [ "$m" != "GET" ]; then
  303. _debug data "$data"
  304. response="$(_post "$data" "$SL_Api/${SL_Ver}${ep}" "" "$m")"
  305. else
  306. response="$(_get "$SL_Api/${SL_Ver}${ep}")"
  307. fi
  308. # shellcheck disable=SC2181
  309. if [ "$?" != "0" ]; then
  310. _err "error $ep"
  311. return 1
  312. fi
  313. _debug2 response "$response"
  314. return 0
  315. }
  316. _get_auth_token() {
  317. if [ "$SL_Ver" = 'v1' ]; then
  318. # token for v1
  319. _debug "Token v1"
  320. _token_keystone=$SL_Key
  321. elif [ "$SL_Ver" = 'v2' ]; then
  322. # token for v2. Get a token for calling the API
  323. _debug "Keystone Token v2"
  324. token_v2=$(_readaccountconf_mutable SL_Token_V2)
  325. if [ -n "$token_v2" ]; then
  326. # The structure with the token was considered. Let's check its validity
  327. # field 1 - SL_Login_Name
  328. # field 2 - token keystone
  329. # field 3 - SL_Login_ID
  330. # field 4 - SL_Project_Name
  331. # field 5 - Receipt time
  332. # separator - '$_sl_sep'
  333. _login_name=$(_getfield "$token_v2" 1 "$_sl_sep")
  334. _token_keystone=$(_getfield "$token_v2" 2 "$_sl_sep")
  335. _project_name=$(_getfield "$token_v2" 4 "$_sl_sep")
  336. _receipt_time=$(_getfield "$token_v2" 5 "$_sl_sep")
  337. _login_id=$(_getfield "$token_v2" 3 "$_sl_sep")
  338. _debug2 _login_name "$_login_name"
  339. _debug2 _login_id "$_login_id"
  340. _debug2 _project_name "$_project_name"
  341. # check the validity of the token for the user and the project and its lifetime
  342. _dt_diff_minute=$((($(date +%s) - _receipt_time) / 60))
  343. _debug2 _dt_diff_minute "$_dt_diff_minute"
  344. [ "$_dt_diff_minute" -gt "$SL_Expire" ] && unset _token_keystone
  345. if [ "$_project_name" != "$SL_Project_Name" ] || [ "$_login_name" != "$SL_Login_Name" ] || [ "$_login_id" != "$SL_Login_ID" ]; then
  346. unset _token_keystone
  347. fi
  348. _debug "Get exists token"
  349. fi
  350. if [ -z "$_token_keystone" ]; then
  351. # the previous token is incorrect or was not received, get a new one
  352. _debug "Update (get new) token"
  353. _data_auth="{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"name\":\"${SL_Login_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"},\"password\":\"${SL_Pswd}\"}}},\"scope\":{\"project\":{\"name\":\"${SL_Project_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"}}}}}"
  354. export _H1="Content-Type: application/json"
  355. _result=$(_post "$_data_auth" "$auth_uri")
  356. _token_keystone=$(grep 'x-subject-token' "$HTTP_HEADER" | sed -nE "s/[[:space:]]*x-subject-token:[[:space:]]*([[:print:]]*)(\r*)/\1/p")
  357. _dt_curr=$(date +%s)
  358. SL_Token_V2="${SL_Login_Name}${_sl_sep}${_token_keystone}${_sl_sep}${SL_Login_ID}${_sl_sep}${SL_Project_Name}${_sl_sep}${_dt_curr}"
  359. _saveaccountconf_mutable SL_Token_V2 "$SL_Token_V2"
  360. fi
  361. else
  362. # token set empty for unsupported version API
  363. _token_keystone=""
  364. fi
  365. printf -- "%s" "$_token_keystone"
  366. }
  367. #################################################################
  368. # use: [non_save]
  369. _sl_init_vars() {
  370. _non_save="${1}"
  371. _debug2 _non_save "$_non_save"
  372. _debug "First init variables"
  373. # version API
  374. SL_Ver="${SL_Ver:-$(_readaccountconf_mutable SL_Ver)}"
  375. if [ -z "$SL_Ver" ]; then
  376. SL_Ver="v1"
  377. fi
  378. if ! [ "$SL_Ver" = "v1" ] && ! [ "$SL_Ver" = "v2" ]; then
  379. _err "You don't specify selectel.ru API version."
  380. _err "Please define specify API version."
  381. fi
  382. _debug2 SL_Ver "$SL_Ver"
  383. if [ "$SL_Ver" = "v1" ]; then
  384. # token
  385. SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}"
  386. if [ -z "$SL_Key" ]; then
  387. SL_Key=""
  388. _err "You don't specify selectel.ru api key yet."
  389. _err "Please create you key and try again."
  390. return 1
  391. fi
  392. #save the api key to the account conf file.
  393. if [ -z "$_non_save" ]; then
  394. _saveaccountconf_mutable SL_Key "$SL_Key"
  395. fi
  396. elif [ "$SL_Ver" = "v2" ]; then
  397. # time expire token
  398. SL_Expire="${SL_Expire:-$(_readaccountconf_mutable SL_Expire)}"
  399. if [ -z "$SL_Expire" ]; then
  400. SL_Expire=1400 # 23h 20 min
  401. fi
  402. if [ -z "$_non_save" ]; then
  403. _saveaccountconf_mutable SL_Expire "$SL_Expire"
  404. fi
  405. # login service user
  406. SL_Login_Name="${SL_Login_Name:-$(_readaccountconf_mutable SL_Login_Name)}"
  407. if [ -z "$SL_Login_Name" ]; then
  408. SL_Login_Name=''
  409. _err "You did not specify the selectel.ru API service user name."
  410. _err "Please provide a service user name and try again."
  411. return 1
  412. fi
  413. if [ -z "$_non_save" ]; then
  414. _saveaccountconf_mutable SL_Login_Name "$SL_Login_Name"
  415. fi
  416. # user ID
  417. SL_Login_ID="${SL_Login_ID:-$(_readaccountconf_mutable SL_Login_ID)}"
  418. if [ -z "$SL_Login_ID" ]; then
  419. SL_Login_ID=''
  420. _err "You did not specify the selectel.ru API user ID."
  421. _err "Please provide a user ID and try again."
  422. return 1
  423. fi
  424. if [ -z "$_non_save" ]; then
  425. _saveaccountconf_mutable SL_Login_ID "$SL_Login_ID"
  426. fi
  427. # project name
  428. SL_Project_Name="${SL_Project_Name:-$(_readaccountconf_mutable SL_Project_Name)}"
  429. if [ -z "$SL_Project_Name" ]; then
  430. SL_Project_Name=''
  431. _err "You did not specify the project name."
  432. _err "Please provide a project name and try again."
  433. return 1
  434. fi
  435. if [ -z "$_non_save" ]; then
  436. _saveaccountconf_mutable SL_Project_Name "$SL_Project_Name"
  437. fi
  438. # service user password
  439. SL_Pswd="${SL_Pswd:-$(_readaccountconf_mutable SL_Pswd)}"
  440. if [ -z "$SL_Pswd" ]; then
  441. SL_Pswd=''
  442. _err "You did not specify the service user password."
  443. _err "Please provide a service user password and try again."
  444. return 1
  445. fi
  446. if [ -z "$_non_save" ]; then
  447. _saveaccountconf_mutable SL_Pswd "$SL_Pswd" "12345678"
  448. fi
  449. else
  450. SL_Ver=""
  451. _err "You also specified the wrong version of the selectel.ru API."
  452. _err "Please provide the correct API version and try again."
  453. return 1
  454. fi
  455. if [ -z "$_non_save" ]; then
  456. _saveaccountconf_mutable SL_Ver "$SL_Ver"
  457. fi
  458. return 0
  459. }