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.

483 lines
16 KiB

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