Hunter0x7c7
2022-08-11 3cd6b479d058b8ee96e1b773c8034f0ca8865f9e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/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 -r 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 -r 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) AND visibility=public"
  _debug filter "$filter"
 
  # List domains and find the zone with the deepest sub-domain (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" "$(echo "$name" | awk -F"." '{print NF-1}')" "$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)
  # starting with version 353.0.0 gcloud seems to
  # separate records with a semicolon instead of commas
  # see also https://cloud.google.com/sdk/docs/release-notes#35300_2021-08-17
  rrdatas=$(echo "$rrdatas" | cut -f2 | sed 's/"[,;]"/"\n"/g')
}