Skip to content

Commit 143aa4f

Browse files
philpennockldez
andauthored
gcloud: support GCE_ZONE_ID to bypass zone list (go-acme#2081)
Co-authored-by: Fernandez Ludovic <[email protected]>
1 parent 9d4c60e commit 143aa4f

File tree

4 files changed

+49
-12
lines changed

4 files changed

+49
-12
lines changed

cmd/zz_gen_cmd_dnshelp.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,7 @@ func displayDNSHelp(w io.Writer, name string) error {
11081108
ew.writeln(` - "GCE_POLLING_INTERVAL": Time between DNS propagation check`)
11091109
ew.writeln(` - "GCE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
11101110
ew.writeln(` - "GCE_TTL": The TTL of the TXT record used for the DNS challenge`)
1111+
ew.writeln(` - "GCE_ZONE_ID": Allows to skip the automatic detection of the zone`)
11111112

11121113
ew.writeln()
11131114
ew.writeln(`More information: https://go-acme.github.io/lego/dns/gcloud`)

docs/content/dns/zz_gen_gcloud.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
5858
| `GCE_POLLING_INTERVAL` | Time between DNS propagation check |
5959
| `GCE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
6060
| `GCE_TTL` | The TTL of the TXT record used for the DNS challenge |
61+
| `GCE_ZONE_ID` | Allows to skip the automatic detection of the zone |
6162

6263
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
6364
More information [here]({{< ref "dns#configuration-and-credentials" >}}).

providers/dns/gcloud/gcloud.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ GCE_PROJECT="gc-project-id" GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.
2121
GCE_SERVICE_ACCOUNT = "Account"
2222
[Configuration.Additional]
2323
GCE_ALLOW_PRIVATE_ZONE = "Allows requested domain to be in private DNS zone, works only with a private ACME server (by default: false)"
24+
GCE_ZONE_ID = "Allows to skip the automatic detection of the zone"
2425
GCE_POLLING_INTERVAL = "Time between DNS propagation check"
2526
GCE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
2627
GCE_TTL = "The TTL of the TXT record used for the DNS challenge"

providers/dns/gcloud/googlecloud.go

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const (
3232

3333
EnvServiceAccount = envNamespace + "SERVICE_ACCOUNT"
3434
EnvProject = envNamespace + "PROJECT"
35+
EnvZoneID = envNamespace + "ZONE_ID"
3536
EnvAllowPrivateZone = envNamespace + "ALLOW_PRIVATE_ZONE"
3637
EnvDebug = envNamespace + "DEBUG"
3738

@@ -44,6 +45,7 @@ const (
4445
type Config struct {
4546
Debug bool
4647
Project string
48+
ZoneID string
4749
AllowPrivateZone bool
4850
PropagationTimeout time.Duration
4951
PollingInterval time.Duration
@@ -55,6 +57,7 @@ type Config struct {
5557
func NewDefaultConfig() *Config {
5658
return &Config{
5759
Debug: env.GetOrDefaultBool(EnvDebug, false),
60+
ZoneID: env.GetOrDefaultString(EnvZoneID, ""),
5861
AllowPrivateZone: env.GetOrDefaultBool(EnvAllowPrivateZone, false),
5962
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
6063
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 180*time.Second),
@@ -310,24 +313,16 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
310313

311314
// getHostedZone returns the managed-zone.
312315
func (d *DNSProvider) getHostedZone(domain string) (string, error) {
313-
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
316+
authZone, zones, err := d.lookupHostedZoneID(domain)
314317
if err != nil {
315-
return "", fmt.Errorf("designate: could not find zone for FQDN %q: %w", domain, err)
318+
return "", err
316319
}
317320

318-
zones, err := d.client.ManagedZones.
319-
List(d.config.Project).
320-
DnsName(authZone).
321-
Do()
322-
if err != nil {
323-
return "", fmt.Errorf("API call failed: %w", err)
324-
}
325-
326-
if len(zones.ManagedZones) == 0 {
321+
if len(zones) == 0 {
327322
return "", fmt.Errorf("no matching domain found for domain %s", authZone)
328323
}
329324

330-
for _, z := range zones.ManagedZones {
325+
for _, z := range zones {
331326
if z.Visibility == "public" || z.Visibility == "" || (z.Visibility == "private" && d.config.AllowPrivateZone) {
332327
return z.Name, nil
333328
}
@@ -340,6 +335,45 @@ func (d *DNSProvider) getHostedZone(domain string) (string, error) {
340335
return "", fmt.Errorf("no public zone found for domain %s", authZone)
341336
}
342337

338+
// lookupHostedZoneID finds the managed zone ID in Google.
339+
//
340+
// Be careful here.
341+
// An automated system might run in a GCloud Service Account, with access to edit the zone
342+
//
343+
// (gcloud dns managed-zones get-iam-policy $zone_id) (role roles/dns.admin)
344+
//
345+
// but not with project-wide access to list all zones
346+
//
347+
// (gcloud projects get-iam-policy $project_id) (a role with permission dns.managedZones.list)
348+
//
349+
// If we force a zone list to succeed, we demand more permissions than needed.
350+
func (d *DNSProvider) lookupHostedZoneID(domain string) (string, []*dns.ManagedZone, error) {
351+
// GCE_ZONE_ID override for service accounts to avoid needing zones-list permission
352+
if d.config.ZoneID != "" {
353+
zone, err := d.client.ManagedZones.Get(d.config.Project, d.config.ZoneID).Do()
354+
if err != nil {
355+
return "", nil, fmt.Errorf("API call ManagedZones.Get for explicit zone ID %q in project %q failed: %w", d.config.ZoneID, d.config.Project, err)
356+
}
357+
358+
return zone.DnsName, []*dns.ManagedZone{zone}, nil
359+
}
360+
361+
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
362+
if err != nil {
363+
return "", nil, fmt.Errorf("could not find zone for FQDN %q: %w", domain, err)
364+
}
365+
366+
zones, err := d.client.ManagedZones.
367+
List(d.config.Project).
368+
DnsName(authZone).
369+
Do()
370+
if err != nil {
371+
return "", nil, fmt.Errorf("API call ManagedZones.List failed: %w", err)
372+
}
373+
374+
return authZone, zones.ManagedZones, nil
375+
}
376+
343377
func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSet, error) {
344378
recs, err := d.client.ResourceRecordSets.List(d.config.Project, zone).Name(fqdn).Type("TXT").Do()
345379
if err != nil {

0 commit comments

Comments
 (0)