Browse Source

Merge pull request #1623 from lenartj/dev

Added support for Google Cloud DNS API (dns_gcloud)
neil 7 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. TELE3 (https://www.tele3.cz)
 1. EUSERV.EU (https://www.euserv.eu)
 1. EUSERV.EU (https://www.euserv.eu)
 1. DNSPod.com API (https://www.dnspod.com)
 1. DNSPod.com API (https://www.dnspod.com)
+1. Google Cloud DNS API
 
 
 And: 
 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.
 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
 ## 47. Use Euserv.eu API
 
 
 First you need to login to your euserv.eu account and activate your API Administration (API Verwaltung).
 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.
 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
 # Use custom API
 
 
 If your API is not supported yet, you can write your own DNS 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')
+}