gcloud: support GCE_ZONE_ID to bypass zone list (#2081)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
This commit is contained in:
parent
9d4c60e67a
commit
143aa4f3ee
4 changed files with 49 additions and 12 deletions
|
@ -1108,6 +1108,7 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln(` - "GCE_POLLING_INTERVAL": Time between DNS propagation check`)
|
ew.writeln(` - "GCE_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||||
ew.writeln(` - "GCE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
ew.writeln(` - "GCE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||||
ew.writeln(` - "GCE_TTL": The TTL of the TXT record used for the DNS challenge`)
|
ew.writeln(` - "GCE_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||||
|
ew.writeln(` - "GCE_ZONE_ID": Allows to skip the automatic detection of the zone`)
|
||||||
|
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/gcloud`)
|
ew.writeln(`More information: https://go-acme.github.io/lego/dns/gcloud`)
|
||||||
|
|
|
@ -58,6 +58,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
||||||
| `GCE_POLLING_INTERVAL` | Time between DNS propagation check |
|
| `GCE_POLLING_INTERVAL` | Time between DNS propagation check |
|
||||||
| `GCE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
| `GCE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
||||||
| `GCE_TTL` | The TTL of the TXT record used for the DNS challenge |
|
| `GCE_TTL` | The TTL of the TXT record used for the DNS challenge |
|
||||||
|
| `GCE_ZONE_ID` | Allows to skip the automatic detection of the zone |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
||||||
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
||||||
|
|
|
@ -21,6 +21,7 @@ GCE_PROJECT="gc-project-id" GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.
|
||||||
GCE_SERVICE_ACCOUNT = "Account"
|
GCE_SERVICE_ACCOUNT = "Account"
|
||||||
[Configuration.Additional]
|
[Configuration.Additional]
|
||||||
GCE_ALLOW_PRIVATE_ZONE = "Allows requested domain to be in private DNS zone, works only with a private ACME server (by default: false)"
|
GCE_ALLOW_PRIVATE_ZONE = "Allows requested domain to be in private DNS zone, works only with a private ACME server (by default: false)"
|
||||||
|
GCE_ZONE_ID = "Allows to skip the automatic detection of the zone"
|
||||||
GCE_POLLING_INTERVAL = "Time between DNS propagation check"
|
GCE_POLLING_INTERVAL = "Time between DNS propagation check"
|
||||||
GCE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
GCE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
||||||
GCE_TTL = "The TTL of the TXT record used for the DNS challenge"
|
GCE_TTL = "The TTL of the TXT record used for the DNS challenge"
|
||||||
|
|
|
@ -32,6 +32,7 @@ const (
|
||||||
|
|
||||||
EnvServiceAccount = envNamespace + "SERVICE_ACCOUNT"
|
EnvServiceAccount = envNamespace + "SERVICE_ACCOUNT"
|
||||||
EnvProject = envNamespace + "PROJECT"
|
EnvProject = envNamespace + "PROJECT"
|
||||||
|
EnvZoneID = envNamespace + "ZONE_ID"
|
||||||
EnvAllowPrivateZone = envNamespace + "ALLOW_PRIVATE_ZONE"
|
EnvAllowPrivateZone = envNamespace + "ALLOW_PRIVATE_ZONE"
|
||||||
EnvDebug = envNamespace + "DEBUG"
|
EnvDebug = envNamespace + "DEBUG"
|
||||||
|
|
||||||
|
@ -44,6 +45,7 @@ const (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
Project string
|
Project string
|
||||||
|
ZoneID string
|
||||||
AllowPrivateZone bool
|
AllowPrivateZone bool
|
||||||
PropagationTimeout time.Duration
|
PropagationTimeout time.Duration
|
||||||
PollingInterval time.Duration
|
PollingInterval time.Duration
|
||||||
|
@ -55,6 +57,7 @@ type Config struct {
|
||||||
func NewDefaultConfig() *Config {
|
func NewDefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Debug: env.GetOrDefaultBool(EnvDebug, false),
|
Debug: env.GetOrDefaultBool(EnvDebug, false),
|
||||||
|
ZoneID: env.GetOrDefaultString(EnvZoneID, ""),
|
||||||
AllowPrivateZone: env.GetOrDefaultBool(EnvAllowPrivateZone, false),
|
AllowPrivateZone: env.GetOrDefaultBool(EnvAllowPrivateZone, false),
|
||||||
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
|
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
|
||||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 180*time.Second),
|
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 180*time.Second),
|
||||||
|
@ -310,24 +313,16 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||||
|
|
||||||
// getHostedZone returns the managed-zone.
|
// getHostedZone returns the managed-zone.
|
||||||
func (d *DNSProvider) getHostedZone(domain string) (string, error) {
|
func (d *DNSProvider) getHostedZone(domain string) (string, error) {
|
||||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
authZone, zones, err := d.lookupHostedZoneID(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("designate: could not find zone for FQDN %q: %w", domain, err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
zones, err := d.client.ManagedZones.
|
if len(zones) == 0 {
|
||||||
List(d.config.Project).
|
|
||||||
DnsName(authZone).
|
|
||||||
Do()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("API call failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(zones.ManagedZones) == 0 {
|
|
||||||
return "", fmt.Errorf("no matching domain found for domain %s", authZone)
|
return "", fmt.Errorf("no matching domain found for domain %s", authZone)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, z := range zones.ManagedZones {
|
for _, z := range zones {
|
||||||
if z.Visibility == "public" || z.Visibility == "" || (z.Visibility == "private" && d.config.AllowPrivateZone) {
|
if z.Visibility == "public" || z.Visibility == "" || (z.Visibility == "private" && d.config.AllowPrivateZone) {
|
||||||
return z.Name, nil
|
return z.Name, nil
|
||||||
}
|
}
|
||||||
|
@ -340,6 +335,45 @@ func (d *DNSProvider) getHostedZone(domain string) (string, error) {
|
||||||
return "", fmt.Errorf("no public zone found for domain %s", authZone)
|
return "", fmt.Errorf("no public zone found for domain %s", authZone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lookupHostedZoneID finds the managed zone ID in Google.
|
||||||
|
//
|
||||||
|
// Be careful here.
|
||||||
|
// An automated system might run in a GCloud Service Account, with access to edit the zone
|
||||||
|
//
|
||||||
|
// (gcloud dns managed-zones get-iam-policy $zone_id) (role roles/dns.admin)
|
||||||
|
//
|
||||||
|
// but not with project-wide access to list all zones
|
||||||
|
//
|
||||||
|
// (gcloud projects get-iam-policy $project_id) (a role with permission dns.managedZones.list)
|
||||||
|
//
|
||||||
|
// If we force a zone list to succeed, we demand more permissions than needed.
|
||||||
|
func (d *DNSProvider) lookupHostedZoneID(domain string) (string, []*dns.ManagedZone, error) {
|
||||||
|
// GCE_ZONE_ID override for service accounts to avoid needing zones-list permission
|
||||||
|
if d.config.ZoneID != "" {
|
||||||
|
zone, err := d.client.ManagedZones.Get(d.config.Project, d.config.ZoneID).Do()
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
return zone.DnsName, []*dns.ManagedZone{zone}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("could not find zone for FQDN %q: %w", domain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zones, err := d.client.ManagedZones.
|
||||||
|
List(d.config.Project).
|
||||||
|
DnsName(authZone).
|
||||||
|
Do()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("API call ManagedZones.List failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return authZone, zones.ManagedZones, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSet, error) {
|
func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSet, error) {
|
||||||
recs, err := d.client.ResourceRecordSets.List(d.config.Project, zone).Name(fqdn).Type("TXT").Do()
|
recs, err := d.client.ResourceRecordSets.List(d.config.Project, zone).Name(fqdn).Type("TXT").Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in a new issue