Browse Source

Merge pull request #1623 from lenartj/dev

Added support for Google Cloud DNS API (dns_gcloud)
neil 6 years ago
parent
commit
329a1e6f16
3 changed files with 191 additions and 0 deletions
  1. 1 0
      README.md
  2. 23 0
      dnsapi/README.md
  3. 167 0
      dnsapi/dns_gcloud.sh

+ 1 - 0
README.md

@@ -322,6 +322,7 @@ You don't have to do anything manually!
 1. TELE3 (https://www.tele3.cz)
 1. EUSERV.EU (https://www.euserv.eu)
 1. DNSPod.com API (https://www.dnspod.com)
+1. Google Cloud DNS API
 
 And: 
 

+ 23 - 0
dnsapi/README.md

@@ -876,6 +876,7 @@ acme.sh --issue --dns dns_tele3 -d example.com -d *.example.com
 ```
 
 The TELE3_Key and TELE3_Secret will be saved in ~/.acme.sh/account.conf and will be reused when needed.
+<<<<<<< HEAD
 ## 47. Use Euserv.eu API
 
 First you need to login to your euserv.eu account and activate your API Administration (API Verwaltung).
@@ -914,6 +915,28 @@ acme.sh --issue --dns dns_dpi -d example.com -d www.example.com
 
 The `DPI_Id` and `DPI_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
+## 49. Use Google Cloud DNS API to automatically issue cert
+
+First you need to authenticate to gcloud.
+
+```
+gcloud init
+```
+
+**The `dns_gcloud` script uses the active gcloud configuration and credentials.**
+There is no logic inside `dns_gcloud` to override the project and other settings.
+If needed, create additional [gcloud configurations](https://cloud.google.com/sdk/gcloud/reference/topic/configurations).
+You can change the configuration being used without *activating* it; simply set the `CLOUDSDK_ACTIVE_CONFIG_NAME` environment variable.
+
+To issue a certificate you can:
+```
+export CLOUDSDK_ACTIVE_CONFIG_NAME=default  # see the note above
+acme.sh --issue --dns dns_gcloud -d example.com -d '*.example.com'
+```
+
+`dns_gcloud` also supports [DNS alias mode](https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode).
+
+=======
 # Use custom API
 
 If your API is not supported yet, you can write your own DNS API.

+ 167 - 0
dnsapi/dns_gcloud.sh

@@ -0,0 +1,167 @@
+#!/usr/bin/env sh
+
+# Author: Janos Lenart <janos@lenart.io>
+
+########  Public functions #####################
+
+# Usage: dns_gcloud_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_gcloud_add() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using gcloud"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  _dns_gcloud_find_zone || return $?
+
+  # Add an extra RR
+  _dns_gcloud_start_tr || return $?
+  _dns_gcloud_get_rrdatas || return $?
+  echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
+  printf "%s\n%s\n" "$rrdatas" "\"$txtvalue\"" | grep -v '^$' | _dns_gcloud_add_rrs || return $?
+  _dns_gcloud_execute_tr || return $?
+
+  _info "$fulldomain record added"
+}
+
+# Usage: dns_gcloud_rm   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Remove the txt record after validation.
+dns_gcloud_rm() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using gcloud"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  _dns_gcloud_find_zone || return $?
+
+  # Remove one RR
+  _dns_gcloud_start_tr || return $?
+  _dns_gcloud_get_rrdatas || return $?
+  echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
+  echo "$rrdatas" | grep -F -v "\"$txtvalue\"" | _dns_gcloud_add_rrs || return $?
+  _dns_gcloud_execute_tr || return $?
+
+  _info "$fulldomain record added"
+}
+
+####################  Private functions below ##################################
+
+_dns_gcloud_start_tr() {
+  if ! trd=$(mktemp -d); then
+    _err "_dns_gcloud_start_tr: failed to create temporary directory"
+    return 1
+  fi
+  tr="$trd/tr.yaml"
+  _debug tr "$tr"
+
+  if ! gcloud dns record-sets transaction start \
+    --transaction-file="$tr" \
+    --zone="$managedZone"; then
+    rm -r "$trd"
+    _err "_dns_gcloud_start_tr: failed to execute transaction"
+    return 1
+  fi
+}
+
+_dns_gcloud_execute_tr() {
+  if ! gcloud dns record-sets transaction execute \
+    --transaction-file="$tr" \
+    --zone="$managedZone"; then
+    _debug tr "$(cat "$tr")"
+    rm -r "$trd"
+    _err "_dns_gcloud_execute_tr: failed to execute transaction"
+    return 1
+  fi
+  rm -r "$trd"
+
+  for i in $(seq 1 120); do
+    if gcloud dns record-sets changes list \
+      --zone="$managedZone" \
+      --filter='status != done' \
+      | grep -q '^.*'; then
+      _info "_dns_gcloud_execute_tr: waiting for transaction to be comitted ($i/120)..."
+      sleep 5
+    else
+      return 0
+    fi
+  done
+
+  _err "_dns_gcloud_execute_tr: transaction is still pending after 10 minutes"
+  rm -r "$trd"
+  return 1
+}
+
+_dns_gcloud_remove_rrs() {
+  if ! xargs --no-run-if-empty gcloud dns record-sets transaction remove \
+    --name="$fulldomain." \
+    --ttl="$ttl" \
+    --type=TXT \
+    --zone="$managedZone" \
+    --transaction-file="$tr"; then
+    _debug tr "$(cat "$tr")"
+    rm -r "$trd"
+    _err "_dns_gcloud_remove_rrs: failed to remove RRs"
+    return 1
+  fi
+}
+
+_dns_gcloud_add_rrs() {
+  ttl=60
+  if ! xargs --no-run-if-empty gcloud dns record-sets transaction add \
+    --name="$fulldomain." \
+    --ttl="$ttl" \
+    --type=TXT \
+    --zone="$managedZone" \
+    --transaction-file="$tr"; then
+    _debug tr "$(cat "$tr")"
+    rm -r "$trd"
+    _err "_dns_gcloud_add_rrs: failed to add RRs"
+    return 1
+  fi
+}
+
+_dns_gcloud_find_zone() {
+  # Prepare a filter that matches zones that are suiteable for this entry.
+  # For example, _acme-challenge.something.domain.com might need to go into something.domain.com or domain.com;
+  # this function finds the longest postfix that has a managed zone.
+  part="$fulldomain"
+  filter="dnsName=( "
+  while [ "$part" != "" ]; do
+    filter="$filter$part. "
+    part="$(echo "$part" | sed 's/[^.]*\.*//')"
+  done
+  filter="$filter)"
+  _debug filter "$filter"
+
+  # List domains and find the longest match (in case of some levels of delegation)
+  if ! match=$(gcloud dns managed-zones list \
+    --format="value(name, dnsName)" \
+    --filter="$filter" \
+    | while read -r dnsName name; do
+      printf "%s\t%s\t%s\n" "${#dnsName}" "$dnsName" "$name"
+    done \
+    | sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then
+    _err "_dns_gcloud_find_zone: Can't find a matching managed zone! Perhaps wrong project or gcloud credentials?"
+    return 1
+  fi
+
+  dnsName=$(echo "$match" | cut -f2)
+  _debug dnsName "$dnsName"
+  managedZone=$(echo "$match" | cut -f1)
+  _debug managedZone "$managedZone"
+}
+
+_dns_gcloud_get_rrdatas() {
+  if ! rrdatas=$(gcloud dns record-sets list \
+    --zone="$managedZone" \
+    --name="$fulldomain." \
+    --type=TXT \
+    --format="value(ttl,rrdatas)"); then
+    _err "_dns_gcloud_get_rrdatas: Failed to list record-sets"
+    rm -r "$trd"
+    return 1
+  fi
+  ttl=$(echo "$rrdatas" | cut -f1)
+  rrdatas=$(echo "$rrdatas" | cut -f2 | sed 's/","/"\n"/g')
+}