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.

518 lines
18 KiB

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