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
17 KiB

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