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.

482 lines
16 KiB

7 years ago
7 years ago
  1. #!/usr/bin/env sh
  2. # Variables that must be defined before running
  3. # export SL_Ver="v1" - version API: 'v2' (actual) or 'v1' (legacy).
  4. # Default: v1
  5. # If SL_Ver="v1"
  6. # export SL_Key="API_Key" - Token Selectel (API key)
  7. # You can view or create in the control panel in the upper right corner, open the menu: "Profile and setting -> Keys API".
  8. # https://my.selectel.ru/profile/apikeys
  9. # If SL_Ver="v2"
  10. # export SL_Expire=60 - token lifetime in minutes (0-1440).
  11. # Default: 1400 minutes
  12. # export SL_Login_ID=<account_id> - account number in the control panel;
  13. # export SL_Project_Name=<project_name> - name project.
  14. # export SL_Login_name=<username> - service user name. You can view the name in the control panel:
  15. # in the upper right corner open menu: "Profile and setting → User management → Service users
  16. # export SL_Pswd='pswd' - service user password, can be viewed when creating a user or changed to a new one.
  17. # All these variables will be saved in ~/.acme.sh/account.conf and will be reused as needed.
  18. #
  19. # Authorization is described in:
  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. _debug3 SL_Ver "$SL_Ver"
  34. _debug3 SL_Expire "$SL_Expire"
  35. _debug3 SL_Login_Name "$SL_Login_Name"
  36. _debug3 SL_Login_ID "$SL_Login_ID"
  37. _debug3 SL_Project_Name "$SL_Project_Name"
  38. _debug "First detect the root zone"
  39. if ! _get_root "$fulldomain"; then
  40. _err "invalid domain"
  41. return 1
  42. fi
  43. _debug _domain_id "$_domain_id"
  44. _debug _sub_domain "$_sub_domain"
  45. _debug _domain "$_domain"
  46. _info "Adding record"
  47. if [ "$SL_Ver" = "v2" ]; then
  48. _ext_srv1="/zones/"
  49. _ext_srv2="/rrset/"
  50. _text_tmp=$(echo "$txtvalue" | sed -En "s/[\"]*([^\"]*)/\1/p")
  51. _text_tmp='\"'$_text_tmp'\"'
  52. _data="{\"type\": \"TXT\", \"ttl\": 60, \"name\": \"${fulldomain}.\", \"records\": [{\"content\":\"$_text_tmp\"}]}"
  53. elif [ "$SL_Ver" = "v1" ]; then
  54. _ext_srv1="/"
  55. _ext_srv2="/records/"
  56. _data="{\"type\":\"TXT\",\"ttl\":60,\"name\":\"$fulldomain\",\"content\":\"$txtvalue\"}"
  57. else
  58. _err "Error. Unsupported version API $SL_Ver"
  59. return 1
  60. fi
  61. _ext_uri="${_ext_srv1}$_domain_id${_ext_srv2}"
  62. _debug _ext_uri "$_ext_uri"
  63. _debug _data "$_data"
  64. if _sl_rest POST "$_ext_uri" "$_data"; then
  65. if _contains "$response" "$txtvalue"; then
  66. _info "Added, OK"
  67. return 0
  68. fi
  69. if _contains "$response" "already_exists"; then
  70. # record TXT with $fulldomain already exists
  71. if [ "$SL_Ver" = "v2" ]; then
  72. # It is necessary to add one more content to the comments
  73. # read all records rrset
  74. _debug "Getting txt records"
  75. _sl_rest GET "${_ext_uri}"
  76. # There is already a $txtvalue value, no need to add it
  77. if _contains "$response" "$txtvalue"; then
  78. _info "Added, OK"
  79. _info "Txt record ${fulldomain} with value ${txtvalue} already exists"
  80. return 0
  81. fi
  82. # group \1 - full record rrset; group \2 - records attribute value, exactly {"content":"\"value1\""},{"content":"\"value2\""}",...
  83. _record_seg="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*${fulldomain}[^}]*records[^}]*\[(\{[^]]*\})\][^}]*}).*/\1/p")"
  84. _record_array="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*${fulldomain}[^}]*records[^}]*\[(\{[^]]*\})\][^}]*}).*/\2/p")"
  85. # record id
  86. _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"")"
  87. # preparing _data
  88. _tmp_str="${_record_array},{\"content\":\"${_text_tmp}\"}"
  89. _data="{\"ttl\": 60, \"records\": [${_tmp_str}]}"
  90. _debug3 _record_seg "$_record_seg"
  91. _debug3 _record_array "$_record_array"
  92. _debug3 _record_array "$_record_id"
  93. _debug "New data for record" "$_data"
  94. if _sl_rest PATCH "${_ext_uri}${_record_id}" "$_data"; then
  95. _info "Added, OK"
  96. return 0
  97. fi
  98. elif [ "$SL_Ver" = "v1" ]; then
  99. _info "Added, OK"
  100. return 0
  101. fi
  102. fi
  103. fi
  104. _err "Add txt record error."
  105. return 1
  106. }
  107. #fulldomain txtvalue
  108. dns_selectel_rm() {
  109. fulldomain=$1
  110. txtvalue=$2
  111. if ! _sl_init_vars "nosave"; then
  112. return 1
  113. fi
  114. _debug3 SL_Ver "$SL_Ver"
  115. _debug3 SL_Expire "$SL_Expire"
  116. _debug3 SL_Login_Name "$SL_Login_Name"
  117. _debug3 SL_Login_ID "$SL_Login_ID"
  118. _debug3 SL_Project_Name "$SL_Project_Name"
  119. #
  120. _debug "First detect the root zone"
  121. if ! _get_root "$fulldomain"; then
  122. _err "invalid domain"
  123. return 1
  124. fi
  125. _debug _domain_id "$_domain_id"
  126. _debug _sub_domain "$_sub_domain"
  127. _debug _domain "$_domain"
  128. #
  129. if [ "$SL_Ver" = "v2" ]; then
  130. _ext_srv1="/zones/"
  131. _ext_srv2="/rrset/"
  132. elif [ "$SL_Ver" = "v1" ]; then
  133. _ext_srv1="/"
  134. _ext_srv2="/records/"
  135. else
  136. _err "Error. Unsupported version API $SL_Ver"
  137. return 1
  138. fi
  139. #
  140. _debug "Getting txt records"
  141. _ext_uri="${_ext_srv1}$_domain_id${_ext_srv2}"
  142. _debug _ext_uri "$_ext_uri"
  143. _sl_rest GET "${_ext_uri}"
  144. #
  145. if ! _contains "$response" "$txtvalue"; then
  146. _err "Txt record not found"
  147. return 1
  148. fi
  149. #
  150. if [ "$SL_Ver" = "v2" ]; then
  151. _record_seg="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\1/gp")"
  152. _record_arr="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\3/p")"
  153. elif [ "$SL_Ver" = "v1" ]; then
  154. _record_seg="$(echo "$response" | _egrep_o "[^{]*\"content\" *: *\"$txtvalue\"[^}]*}")"
  155. else
  156. _err "Error. Unsupported version API $SL_Ver"
  157. return 1
  158. fi
  159. _debug3 "_record_seg" "$_record_seg"
  160. if [ -z "$_record_seg" ]; then
  161. _err "can not find _record_seg"
  162. return 1
  163. fi
  164. # record id
  165. # the following lines change the algorithm for deleting records with the value $txtvalue
  166. # if you use the 1st line, then all such records are deleted at once
  167. # if you use the 2nd line, then only the first entry from them is deleted
  168. #_record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"")"
  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. _debug _del_uri "$_del_uri"
  182. if [ -z "$_new_arr" ]; then
  183. # remove record
  184. if ! _sl_rest DELETE "${_del_uri}"; then
  185. _err "Delete record error: ${_del_uri}."
  186. else
  187. info "Delete record success: ${_del_uri}."
  188. fi
  189. else
  190. # update a record by removing one element in content
  191. _data="{\"ttl\": 60, \"records\": [${_new_arr}]}"
  192. _debug3 _data "$_data"
  193. # REST API PATCH call
  194. if _sl_rest PATCH "${_del_uri}" "$_data"; then
  195. _info "Patched, OK: ${_del_uri}"
  196. else
  197. _err "Patched record error: ${_del_uri}."
  198. fi
  199. fi
  200. else
  201. # legacy
  202. for _one_id in $_record_id; do
  203. _del_uri="${_ext_uri}${_one_id}"
  204. _debug _del_uri "$_del_uri"
  205. if ! _sl_rest DELETE "${_del_uri}"; then
  206. _err "Delete record error: ${_del_uri}."
  207. else
  208. info "Delete record success: ${_del_uri}."
  209. fi
  210. done
  211. fi
  212. return 0
  213. }
  214. #################### Private functions below ##################################
  215. _get_root() {
  216. domain=$1
  217. if [ "$SL_Ver" = 'v1' ]; then
  218. # version API 1
  219. if ! _sl_rest GET "/"; then
  220. return 1
  221. fi
  222. i=2
  223. p=1
  224. while true; do
  225. h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
  226. _debug h "$h"
  227. if [ -z "$h" ]; then
  228. return 1
  229. fi
  230. if _contains "$response" "\"name\" *: *\"$h\","; then
  231. _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
  232. _domain=$h
  233. _debug "Getting domain id for $h"
  234. if ! _sl_rest GET "/$h"; then
  235. _err "Error read records of all domains $SL_Ver"
  236. return 1
  237. fi
  238. _domain_id="$(echo "$response" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\":" | cut -d : -f 2)"
  239. return 0
  240. fi
  241. p=$i
  242. i=$(_math "$i" + 1)
  243. done
  244. _err "Error read records of all domains $SL_Ver"
  245. return 1
  246. elif [ "$SL_Ver" = "v2" ]; then
  247. # version API 2
  248. _ext_uri='/zones/'
  249. domain="${domain}."
  250. _debug "domain:: " "$domain"
  251. # read records of all domains
  252. if ! _sl_rest GET "$_ext_uri"; then
  253. _err "Error read records of all domains $SL_Ver"
  254. return 1
  255. fi
  256. i=2
  257. p=1
  258. while true; do
  259. h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
  260. _debug h "$h"
  261. if [ -z "$h" ]; then
  262. _err "The domain was not found among the registered ones"
  263. return 1
  264. fi
  265. _domain_record=$(echo "$response" | sed -En "s/.*(\{[^}]*id[^}]*\"name\" *: *\"$h\"[^}]*}).*/\1/p")
  266. _debug "_domain_record:: " "$_domain_record"
  267. if [ -n "$_domain_record" ]; then
  268. _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
  269. _domain=$h
  270. _debug "Getting domain id for $h"
  271. _domain_id=$(echo "$_domain_record" | sed -En "s/\{[^}]*\"id\" *: *\"([^\"]*)\"[^}]*\}/\1/p")
  272. return 0
  273. fi
  274. p=$i
  275. i=$(_math "$i" + 1)
  276. done
  277. _err "Error read records of all domains $SL_Ver"
  278. return 1
  279. else
  280. _err "Error. Unsupported version API $SL_Ver"
  281. return 1
  282. fi
  283. }
  284. #################################################################
  285. # use: method add_url body
  286. _sl_rest() {
  287. m=$1
  288. ep="$2"
  289. data="$3"
  290. _token=$(_get_auth_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="v1"
  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. if [ -z "$SL_Pswd" ]; then
  444. SL_Pswd=''
  445. _err "You did not specify the service user password."
  446. _err "Please provide a service user password and try again."
  447. return 1
  448. fi
  449. if [ -z "$_non_save" ]; then
  450. _saveaccountconf_mutable SL_Pswd "$SL_Pswd" "12345678"
  451. fi
  452. else
  453. SL_Ver=""
  454. _err "You also specified the wrong version of the selectel.ru API."
  455. _err "Please provide the correct API version and try again."
  456. return 1
  457. fi
  458. if [ -z "$_non_save" ]; then
  459. _saveaccountconf_mutable SL_Ver "$SL_Ver"
  460. fi
  461. return 0
  462. }