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.

222 lines
6.4 KiB

3 years ago
4 years ago
4 years ago
4 years ago
  1. #!/usr/bin/env sh
  2. # Author: Janos Lenart <janos@lenart.io>
  3. ######## Public functions #####################
  4. # Usage: dns_gcloud_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
  5. dns_gcloud_add() {
  6. fulldomain=$1
  7. txtvalue=$2
  8. _info "Using gcloud"
  9. _debug fulldomain "$fulldomain"
  10. _debug txtvalue "$txtvalue"
  11. _dns_gcloud_authenticate || return $?
  12. _dns_gcloud_find_zone || return $?
  13. # Add an extra RR
  14. _dns_gcloud_start_tr || return $?
  15. _dns_gcloud_get_rrdatas || return $?
  16. echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
  17. printf "%s\n%s\n" "$rrdatas" "\"$txtvalue\"" | grep -v '^$' | _dns_gcloud_add_rrs || return $?
  18. _dns_gcloud_execute_tr || return $?
  19. _info "$fulldomain record added"
  20. }
  21. # Usage: dns_gcloud_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
  22. # Remove the txt record after validation.
  23. dns_gcloud_rm() {
  24. fulldomain=$1
  25. txtvalue=$2
  26. _info "Using gcloud"
  27. _debug fulldomain "$fulldomain"
  28. _debug txtvalue "$txtvalue"
  29. _dns_gcloud_authenticate || return $?
  30. _dns_gcloud_find_zone || return $?
  31. # Remove one RR
  32. _dns_gcloud_start_tr || return $?
  33. _dns_gcloud_get_rrdatas || return $?
  34. echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
  35. echo "$rrdatas" | grep -F -v "\"$txtvalue\"" | _dns_gcloud_add_rrs || return $?
  36. _dns_gcloud_execute_tr || return $?
  37. _info "$fulldomain record added"
  38. }
  39. #################### Private functions below ##################################
  40. _dns_gcloud_authenticate() {
  41. _info "_dns_gcloud_authenticate: authenticating gcloud"
  42. _debug "_dns_gcloud_authenticate: checking authenticated status"
  43. account=$(
  44. gcloud auth list \
  45. --filter "status:ACTIVE" \
  46. --format "value(account)" \
  47. --verbosity error
  48. )
  49. if [ "$account" ]; then
  50. _info "_dns_gcloud_authenticate: already authenticated"
  51. return 0
  52. fi
  53. _debug "_dns_gcloud_authenticate: attempting to authenticate using service account key"
  54. GCLOUD_Service_Account_Key="${GCLOUD_Service_Account_Key:-$(_readaccountconf_mutable GCLOUD_Service_Account_Key)}"
  55. GCLOUD_Project_ID="${GCLOUD_Project_ID:-$(_readaccountconf_mutable GCLOUD_Project_ID)}"
  56. if [ -z "$GCLOUD_Service_Account_Key" ]; then
  57. GCLOUD_Service_Account_Key=""
  58. GCLOUD_Project_ID=""
  59. _err "_dns_gcloud_authenticate: missing Google Cloud service account key"
  60. return 1
  61. fi
  62. if [ -z "$GCLOUD_Project_ID" ]; then
  63. GCLOUD_Service_Account_Key=""
  64. GCLOUD_Project_ID=""
  65. _err "_dns_gcloud_authenticate: missing Google Cloud project ID"
  66. return 1
  67. fi
  68. if ! echo "$GCLOUD_Service_Account_Key" | gcloud auth activate-service-account --key-file -; then
  69. _err "_dns_gcloud_authenticate: failed to authenticate with service account key"
  70. return 1
  71. fi
  72. _info "_dns_gcloud_authenticate: successfully authenticated using service account key"
  73. gcloud config set project "$GCLOUD_Project_ID"
  74. _info "_dns_gcloud_authenticate: configured gcloud project"
  75. #save the service account api key and project ID to the account conf file.
  76. _saveaccountconf_mutable GCLOUD_Service_Account_Key "$GCLOUD_Service_Account_Key"
  77. _saveaccountconf_mutable GCLOUD_Project_ID "$GCLOUD_Project_ID"
  78. }
  79. _dns_gcloud_start_tr() {
  80. if ! trd=$(mktemp -d); then
  81. _err "_dns_gcloud_start_tr: failed to create temporary directory"
  82. return 1
  83. fi
  84. tr="$trd/tr.yaml"
  85. _debug tr "$tr"
  86. if ! gcloud dns record-sets transaction start \
  87. --transaction-file="$tr" \
  88. --zone="$managedZone"; then
  89. rm -r "$trd"
  90. _err "_dns_gcloud_start_tr: failed to execute transaction"
  91. return 1
  92. fi
  93. }
  94. _dns_gcloud_execute_tr() {
  95. if ! gcloud dns record-sets transaction execute \
  96. --transaction-file="$tr" \
  97. --zone="$managedZone"; then
  98. _debug tr "$(cat "$tr")"
  99. rm -r "$trd"
  100. _err "_dns_gcloud_execute_tr: failed to execute transaction"
  101. return 1
  102. fi
  103. rm -r "$trd"
  104. for i in $(seq 1 120); do
  105. if gcloud dns record-sets changes list \
  106. --zone="$managedZone" \
  107. --filter='status != done' |
  108. grep -q '^.*'; then
  109. _info "_dns_gcloud_execute_tr: waiting for transaction to be comitted ($i/120)..."
  110. sleep 5
  111. else
  112. return 0
  113. fi
  114. done
  115. _err "_dns_gcloud_execute_tr: transaction is still pending after 10 minutes"
  116. rm -r "$trd"
  117. return 1
  118. }
  119. _dns_gcloud_remove_rrs() {
  120. if ! xargs -r gcloud dns record-sets transaction remove \
  121. --name="$fulldomain." \
  122. --ttl="$ttl" \
  123. --type=TXT \
  124. --zone="$managedZone" \
  125. --transaction-file="$tr"; then
  126. _debug tr "$(cat "$tr")"
  127. rm -r "$trd"
  128. _err "_dns_gcloud_remove_rrs: failed to remove RRs"
  129. return 1
  130. fi
  131. }
  132. _dns_gcloud_add_rrs() {
  133. ttl=60
  134. if ! xargs -r gcloud dns record-sets transaction add \
  135. --name="$fulldomain." \
  136. --ttl="$ttl" \
  137. --type=TXT \
  138. --zone="$managedZone" \
  139. --transaction-file="$tr"; then
  140. _debug tr "$(cat "$tr")"
  141. rm -r "$trd"
  142. _err "_dns_gcloud_add_rrs: failed to add RRs"
  143. return 1
  144. fi
  145. }
  146. _dns_gcloud_find_zone() {
  147. # Prepare a filter that matches zones that are suiteable for this entry.
  148. # For example, _acme-challenge.something.domain.com might need to go into something.domain.com or domain.com;
  149. # this function finds the longest postfix that has a managed zone.
  150. part="$fulldomain"
  151. filter="dnsName=( "
  152. while [ "$part" != "" ]; do
  153. filter="$filter$part. "
  154. part="$(echo "$part" | sed 's/[^.]*\.*//')"
  155. done
  156. filter="$filter) AND visibility=public"
  157. _debug filter "$filter"
  158. # List domains and find the zone with the deepest sub-domain (in case of some levels of delegation)
  159. if ! match=$(gcloud dns managed-zones list \
  160. --format="value(name, dnsName)" \
  161. --filter="$filter" |
  162. while read -r dnsName name; do
  163. printf "%s\t%s\t%s\n" "$(echo "$name" | awk -F"." '{print NF-1}')" "$dnsName" "$name"
  164. done |
  165. sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then
  166. _err "_dns_gcloud_find_zone: Can't find a matching managed zone! Perhaps wrong project or gcloud credentials?"
  167. return 1
  168. fi
  169. dnsName=$(echo "$match" | cut -f2)
  170. _debug dnsName "$dnsName"
  171. managedZone=$(echo "$match" | cut -f1)
  172. _debug managedZone "$managedZone"
  173. }
  174. _dns_gcloud_get_rrdatas() {
  175. if ! rrdatas=$(gcloud dns record-sets list \
  176. --zone="$managedZone" \
  177. --name="$fulldomain." \
  178. --type=TXT \
  179. --format="value(ttl,rrdatas)"); then
  180. _err "_dns_gcloud_get_rrdatas: Failed to list record-sets"
  181. rm -r "$trd"
  182. return 1
  183. fi
  184. ttl=$(echo "$rrdatas" | cut -f1)
  185. rrdatas=$(echo "$rrdatas" | cut -f2 | sed 's/","/"\n"/g')
  186. }