traefik/vendor/github.com/xenolf/lego/providers/dns/gcloud/googlecloud.go

215 lines
5.4 KiB
Go
Raw Normal View History

2018-05-31 07:30:04 +00:00
// Package gcloud implements a DNS provider for solving the DNS-01
2017-02-07 21:33:23 +00:00
// challenge using Google Cloud DNS.
2018-05-31 07:30:04 +00:00
package gcloud
2017-02-07 21:33:23 +00:00
import (
2018-05-31 07:30:04 +00:00
"encoding/json"
2017-02-07 21:33:23 +00:00
"fmt"
2017-10-31 09:42:03 +00:00
"io/ioutil"
2017-02-07 21:33:23 +00:00
"os"
"time"
2018-05-31 07:30:04 +00:00
"github.com/xenolf/lego/acme"
2017-02-07 21:33:23 +00:00
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
"google.golang.org/api/dns/v1"
)
// DNSProvider is an implementation of the DNSProvider interface.
type DNSProvider struct {
project string
client *dns.Service
}
// NewDNSProvider returns a DNSProvider instance configured for Google Cloud
2017-10-31 09:42:03 +00:00
// DNS. Project name must be passed in the environment variable: GCE_PROJECT.
// A Service Account file can be passed in the environment variable:
// GCE_SERVICE_ACCOUNT_FILE
2017-02-07 21:33:23 +00:00
func NewDNSProvider() (*DNSProvider, error) {
2017-10-31 09:42:03 +00:00
if saFile, ok := os.LookupEnv("GCE_SERVICE_ACCOUNT_FILE"); ok {
2018-05-31 07:30:04 +00:00
return NewDNSProviderServiceAccount(saFile)
2017-10-31 09:42:03 +00:00
}
2018-07-03 10:44:04 +00:00
2018-05-31 07:30:04 +00:00
project := os.Getenv("GCE_PROJECT")
2017-02-07 21:33:23 +00:00
return NewDNSProviderCredentials(project)
}
// NewDNSProviderCredentials uses the supplied credentials to return a
// DNSProvider instance configured for Google Cloud DNS.
func NewDNSProviderCredentials(project string) (*DNSProvider, error) {
if project == "" {
return nil, fmt.Errorf("Google Cloud project name missing")
}
client, err := google.DefaultClient(context.Background(), dns.NdevClouddnsReadwriteScope)
if err != nil {
2018-07-03 10:44:04 +00:00
return nil, fmt.Errorf("unable to get Google Cloud client: %v", err)
2017-02-07 21:33:23 +00:00
}
svc, err := dns.New(client)
if err != nil {
2018-07-03 10:44:04 +00:00
return nil, fmt.Errorf("unable to create Google Cloud DNS service: %v", err)
2017-02-07 21:33:23 +00:00
}
return &DNSProvider{
project: project,
client: svc,
}, nil
}
2017-10-31 09:42:03 +00:00
// NewDNSProviderServiceAccount uses the supplied service account JSON file to
// return a DNSProvider instance configured for Google Cloud DNS.
2018-05-31 07:30:04 +00:00
func NewDNSProviderServiceAccount(saFile string) (*DNSProvider, error) {
2017-10-31 09:42:03 +00:00
if saFile == "" {
return nil, fmt.Errorf("Google Cloud Service Account file missing")
}
dat, err := ioutil.ReadFile(saFile)
if err != nil {
2018-07-03 10:44:04 +00:00
return nil, fmt.Errorf("unable to read Service Account file: %v", err)
2017-10-31 09:42:03 +00:00
}
2018-05-31 07:30:04 +00:00
// read project id from service account file
var datJSON struct {
ProjectID string `json:"project_id"`
}
err = json.Unmarshal(dat, &datJSON)
if err != nil || datJSON.ProjectID == "" {
2018-07-03 10:44:04 +00:00
return nil, fmt.Errorf("project ID not found in Google Cloud Service Account file")
2018-05-31 07:30:04 +00:00
}
project := datJSON.ProjectID
2017-10-31 09:42:03 +00:00
conf, err := google.JWTConfigFromJSON(dat, dns.NdevClouddnsReadwriteScope)
if err != nil {
2018-07-03 10:44:04 +00:00
return nil, fmt.Errorf("unable to acquire config: %v", err)
2017-10-31 09:42:03 +00:00
}
2018-05-31 07:30:04 +00:00
client := conf.Client(context.Background())
2017-10-31 09:42:03 +00:00
svc, err := dns.New(client)
if err != nil {
2018-07-03 10:44:04 +00:00
return nil, fmt.Errorf("unable to create Google Cloud DNS service: %v", err)
2017-10-31 09:42:03 +00:00
}
return &DNSProvider{
project: project,
client: svc,
}, nil
}
2017-02-07 21:33:23 +00:00
// Present creates a TXT record to fulfil the dns-01 challenge.
2018-07-03 10:44:04 +00:00
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
2018-05-31 07:30:04 +00:00
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
2017-02-07 21:33:23 +00:00
2018-07-03 10:44:04 +00:00
zone, err := d.getHostedZone(domain)
2017-02-07 21:33:23 +00:00
if err != nil {
return err
}
rec := &dns.ResourceRecordSet{
Name: fqdn,
Rrdatas: []string{value},
Ttl: int64(ttl),
Type: "TXT",
}
change := &dns.Change{
Additions: []*dns.ResourceRecordSet{rec},
}
// Look for existing records.
2018-07-03 10:44:04 +00:00
list, err := d.client.ResourceRecordSets.List(d.project, zone).Name(fqdn).Type("TXT").Do()
2017-02-07 21:33:23 +00:00
if err != nil {
return err
}
if len(list.Rrsets) > 0 {
// Attempt to delete the existing records when adding our new one.
change.Deletions = list.Rrsets
}
2018-07-03 10:44:04 +00:00
chg, err := d.client.Changes.Create(d.project, zone, change).Do()
2017-02-07 21:33:23 +00:00
if err != nil {
return err
}
// wait for change to be acknowledged
for chg.Status == "pending" {
time.Sleep(time.Second)
2018-07-03 10:44:04 +00:00
chg, err = d.client.Changes.Get(d.project, zone, chg.Id).Do()
2017-02-07 21:33:23 +00:00
if err != nil {
return err
}
}
return nil
}
// CleanUp removes the TXT record matching the specified parameters.
2018-07-03 10:44:04 +00:00
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
2018-05-31 07:30:04 +00:00
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
2017-02-07 21:33:23 +00:00
2018-07-03 10:44:04 +00:00
zone, err := d.getHostedZone(domain)
2017-02-07 21:33:23 +00:00
if err != nil {
return err
}
2018-07-03 10:44:04 +00:00
records, err := d.findTxtRecords(zone, fqdn)
2017-02-07 21:33:23 +00:00
if err != nil {
return err
}
for _, rec := range records {
change := &dns.Change{
Deletions: []*dns.ResourceRecordSet{rec},
}
2018-07-03 10:44:04 +00:00
_, err = d.client.Changes.Create(d.project, zone, change).Do()
2017-02-07 21:33:23 +00:00
if err != nil {
return err
}
}
return nil
}
// Timeout customizes the timeout values used by the ACME package for checking
// DNS record validity.
2018-07-03 10:44:04 +00:00
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
2017-02-07 21:33:23 +00:00
return 180 * time.Second, 5 * time.Second
}
// getHostedZone returns the managed-zone
2018-07-03 10:44:04 +00:00
func (d *DNSProvider) getHostedZone(domain string) (string, error) {
2018-05-31 07:30:04 +00:00
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
2017-02-07 21:33:23 +00:00
if err != nil {
return "", err
}
2018-07-03 10:44:04 +00:00
zones, err := d.client.ManagedZones.
List(d.project).
2017-02-07 21:33:23 +00:00
DnsName(authZone).
Do()
if err != nil {
return "", fmt.Errorf("GoogleCloud API call failed: %v", err)
}
if len(zones.ManagedZones) == 0 {
2018-07-03 10:44:04 +00:00
return "", fmt.Errorf("no matching GoogleCloud domain found for domain %s", authZone)
2017-02-07 21:33:23 +00:00
}
return zones.ManagedZones[0].Name, nil
}
2018-07-03 10:44:04 +00:00
func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSet, error) {
2017-02-07 21:33:23 +00:00
2018-07-03 10:44:04 +00:00
recs, err := d.client.ResourceRecordSets.List(d.project, zone).Do()
2017-02-07 21:33:23 +00:00
if err != nil {
return nil, err
}
2018-07-03 10:44:04 +00:00
var found []*dns.ResourceRecordSet
2017-02-07 21:33:23 +00:00
for _, r := range recs.Rrsets {
if r.Type == "TXT" && r.Name == fqdn {
found = append(found, r)
}
}
return found, nil
}