func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { // Find the zone for the given fqdn zone, err := acme.FindZoneByFqdn(fqdn, []string{r.nameserver}) if err != nil { return err } // Create RR rr := new(dns.TXT) rr.Hdr = dns.RR_Header{Name: fqdn, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: uint32(ttl)} rr.Txt = []string{value} rrs := []dns.RR{rr} // Create dynamic update packet m := new(dns.Msg) m.SetUpdate(zone) switch action { case "INSERT": // Always remove old challenge left over from who knows what. m.RemoveRRset(rrs) m.Insert(rrs) case "REMOVE": m.Remove(rrs) default: return fmt.Errorf("Unexpected action: %s", action) } // Setup client c := new(dns.Client) c.SingleInflight = true // TSIG authentication / msg signing if len(r.tsigKey) > 0 && len(r.tsigSecret) > 0 { m.SetTsig(dns.Fqdn(r.tsigKey), r.tsigAlgorithm, 300, time.Now().Unix()) c.TsigSecret = map[string]string{dns.Fqdn(r.tsigKey): r.tsigSecret} } // Send the query reply, _, err := c.Exchange(m, r.nameserver) if err != nil { return fmt.Errorf("DNS update failed: %v", err) } if reply != nil && reply.Rcode != dns.RcodeSuccess { return fmt.Errorf("DNS update failed. Server replied: %s", dns.RcodeToString[reply.Rcode]) } return nil }
func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { // HostedZone represents a CloudFlare DNS zone type HostedZone struct { ID string `json:"id"` Name string `json:"name"` } authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return "", err } result, err := c.makeRequest("GET", "/zones?name="+acme.UnFqdn(authZone), nil) if err != nil { return "", err } var hostedZone []HostedZone err = json.Unmarshal(result, &hostedZone) if err != nil { return "", err } if len(hostedZone) != 1 { return "", fmt.Errorf("Zone %s not found in CloudFlare for domain %s", authZone, fqdn) } return hostedZone[0].ID, nil }
// getHostedZoneID performs a lookup to get the DNS zone which needs // modifying for a given FQDN func (c *DNSProvider) getHostedZoneID(fqdn string) (int, error) { // HostedZones represents the response when querying Rackspace DNS zones type ZoneSearchResponse struct { TotalEntries int `json:"totalEntries"` HostedZones []struct { ID int `json:"id"` Name string `json:"name"` } `json:"domains"` } authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return 0, err } result, err := c.makeRequest("GET", fmt.Sprintf("/domains?name=%s", acme.UnFqdn(authZone)), nil) if err != nil { return 0, err } var zoneSearchResponse ZoneSearchResponse err = json.Unmarshal(result, &zoneSearchResponse) if err != nil { return 0, err } // If nothing was returned, or for whatever reason more than 1 was returned (the search uses exact match, so should not occur) if zoneSearchResponse.TotalEntries != 1 { return 0, fmt.Errorf("Found %d zones for %s in Rackspace for domain %s", zoneSearchResponse.TotalEntries, authZone, fqdn) } return zoneSearchResponse.HostedZones[0].ID, nil }
func (c *DNSProvider) getHostedZone(domain string) (string, string, error) { zones, _, err := c.client.Domains.List() if err != nil { return "", "", fmt.Errorf("dnspod API call failed: %v", err) } authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return "", "", err } var hostedZone dnspod.Domain for _, zone := range zones { if zone.Name == acme.UnFqdn(authZone) { hostedZone = zone } } if hostedZone.ID == 0 { return "", "", fmt.Errorf("Zone %s not found in dnspod for domain %s", authZone, domain) } return fmt.Sprintf("%v", hostedZone.ID), hostedZone.Name, nil }
// CleanUp removes the TXT record matching the specified parameters func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { fqdn, _, _ := acme.DNS01Record(domain, keyAuth) // get the record's unique ID from when we created it d.recordIDsMu.Lock() recordID, ok := d.recordIDs[fqdn] d.recordIDsMu.Unlock() if !ok { return fmt.Errorf("unknown record ID for '%s'", fqdn) } authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) } authZone = acme.UnFqdn(authZone) reqURL := fmt.Sprintf("/domain/zone/%s/record/%d", authZone, recordID) err = d.client.Delete(reqURL, nil) if err != nil { fmt.Printf("Error when call OVH api to delete challenge record : %q \n", err) return err } // Delete record ID from map d.recordIDsMu.Lock() delete(d.recordIDs, fqdn) d.recordIDsMu.Unlock() return nil }
func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) { authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return "", err } // .DNSName should not have a trailing dot reqParams := &route53.ListHostedZonesByNameInput{ DNSName: aws.String(acme.UnFqdn(authZone)), } resp, err := r.client.ListHostedZonesByName(reqParams) if err != nil { return "", err } var hostedZoneID string for _, hostedZone := range resp.HostedZones { // .Name has a trailing dot if !*hostedZone.Config.PrivateZone && *hostedZone.Name == authZone { hostedZoneID = *hostedZone.Id break } } if len(hostedZoneID) == 0 { return "", fmt.Errorf("Zone %s not found in Route 53 for domain %s", authZone, fqdn) } if strings.HasPrefix(hostedZoneID, "/hostedzone/") { hostedZoneID = strings.TrimPrefix(hostedZoneID, "/hostedzone/") } return hostedZoneID, nil }
// CleanUp removes a given record that was generated by Present func (provider *DNSProvider) CleanUp(domain, token, keyAuth string) error { fqdn, _, _ := acme.DNS01Record(domain, keyAuth) provider.recordIDsMu.Lock() recordID, ok := provider.recordIDs[fqdn] provider.recordIDsMu.Unlock() if !ok { return fmt.Errorf("Unknown recordID for '%s'", fqdn) } authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) } authZone = acme.UnFqdn(authZone) zoneRecord, err := provider.getZoneInformationByName(authZone) if err != nil { return err } _, err = provider.client.RemoveRecord(zoneRecord.ID, recordID) if err != nil { return err } provider.recordIDsMu.Lock() delete(provider.recordIDs, fqdn) provider.recordIDsMu.Unlock() return nil }
// Present creates a TXT record to fulfil the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { // txtRecordRequest represents the request body to DO's API to make a TXT record type txtRecordRequest struct { FieldType string `json:"fieldType"` SubDomain string `json:"subDomain"` Target string `json:"target"` TTL int `json:"ttl"` } // txtRecordResponse represents a response from DO's API after making a TXT record type txtRecordResponse struct { ID int `json:"id"` FieldType string `json:"fieldType"` SubDomain string `json:"subDomain"` Target string `json:"target"` TTL int `json:"ttl"` Zone string `json:"zone"` } fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) // Parse domain name authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) } authZone = acme.UnFqdn(authZone) subDomain := d.extractRecordName(fqdn, authZone) reqURL := fmt.Sprintf("/domain/zone/%s/record", authZone) reqData := txtRecordRequest{FieldType: "TXT", SubDomain: subDomain, Target: value, TTL: ttl} var respData txtRecordResponse // Create TXT record err = d.client.Post(reqURL, reqData, &respData) if err != nil { fmt.Printf("Error when call OVH api to add record : %q \n", err) return err } // Apply the change reqURL = fmt.Sprintf("/domain/zone/%s/refresh", authZone) err = d.client.Post(reqURL, nil, nil) if err != nil { fmt.Printf("Error when call OVH api to refresh zone : %q \n", err) return err } d.recordIDsMu.Lock() d.recordIDs[fqdn] = respData.ID d.recordIDsMu.Unlock() return nil }
// Extract DNS zone and DNS entry name func (c *DNSProvider) FindZoneAndRecordName(fqdn, domain string) (string, string, error) { zone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return "", "", err } zone = acme.UnFqdn(zone) name := acme.UnFqdn(fqdn) name = name[:len(name)-len("."+zone)] return zone, name, nil }
func (c *DNSProvider) getHostedZone(fqdn string) (*hostedZone, error) { var zone hostedZone authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return nil, err } url := "/servers/localhost/zones" result, err := c.makeRequest("GET", url, nil) if err != nil { return nil, err } zones := []hostedZone{} err = json.Unmarshal(result, &zones) if err != nil { return nil, err } url = "" for _, zone := range zones { if acme.UnFqdn(zone.Name) == acme.UnFqdn(authZone) { url = zone.URL } } result, err = c.makeRequest("GET", url, nil) if err != nil { return nil, err } err = json.Unmarshal(result, &zone) if err != nil { return nil, err } // convert pre-v1 API result if len(zone.Records) > 0 { zone.RRSets = []rrSet{} for _, record := range zone.Records { set := rrSet{ Name: record.Name, Type: record.Type, Records: []pdnsRecord{record}, } zone.RRSets = append(zone.RRSets, set) } } return &zone, nil }
// CleanUp removes the TXT record matching the specified parameters func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { fqdn, _, _ := acme.DNS01Record(domain, keyAuth) // get the record's unique ID from when we created it d.recordIDsMu.Lock() recordID, ok := d.recordIDs[fqdn] d.recordIDsMu.Unlock() if !ok { return fmt.Errorf("unknown record ID for '%s'", fqdn) } authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) } authZone = acme.UnFqdn(authZone) reqURL := fmt.Sprintf("%s/v2/domains/%s/records/%d", digitalOceanBaseURL, authZone, recordID) req, err := http.NewRequest("DELETE", reqURL, nil) if err != nil { return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", d.apiAuthToken)) client := http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 400 { var errInfo digitalOceanAPIError json.NewDecoder(resp.Body).Decode(&errInfo) return fmt.Errorf("HTTP %d: %s: %s", resp.StatusCode, errInfo.ID, errInfo.Message) } // Delete record ID from map d.recordIDsMu.Lock() delete(d.recordIDs, fqdn) d.recordIDsMu.Unlock() return nil }
// CleanUp removes the TXT record matching the specified parameters func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { fqdn, _, _ := acme.DNS01Record(domain, keyAuth) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return err } err = d.login() if err != nil { return err } resource := fmt.Sprintf("TXTRecord/%s/%s/", authZone, fqdn) url := fmt.Sprintf("%s/%s", dynBaseURL, resource) req, err := http.NewRequest("DELETE", url, nil) if err != nil { return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Auth-Token", d.token) client := &http.Client{Timeout: time.Duration(10 * time.Second)} resp, err := client.Do(req) if err != nil { return err } resp.Body.Close() if resp.StatusCode != 200 { return fmt.Errorf("Dyn API request failed to delete TXT record HTTP status code %d", resp.StatusCode) } err = d.publish(authZone, "Removed TXT record for ACME dns-01 challenge using lego client") if err != nil { return err } err = d.logout() if err != nil { return err } return nil }
// Checks that azure has a zone for this domain name. func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return "", err } // Now we want to to Azure and get the zone. dc := dns.NewZonesClient(c.subscriptionId) dc.Authorizer, err = c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint) zone, err := dc.Get(c.resourceGroup, acme.UnFqdn(authZone)) if err != nil { return "", err } // zone.Name shouldn't have a trailing dot(.) return to.String(zone.Name), nil }
func (p *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) { // Lookup the zone that handles the specified FQDN. authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return nil, err } resourceName := strings.TrimSuffix(fqdn, "."+authZone) // Query the authority zone. domain, err := p.linode.GetDomain(acme.UnFqdn(authZone)) if err != nil { return nil, err } return &hostedZoneInfo{ domainId: domain.DomainID, resourceName: resourceName, }, nil }
// getHostedZone returns the managed-zone func (c *DNSProvider) getHostedZone(domain string) (string, error) { authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return "", err } zones, err := c.client.ManagedZones. List(c.project). DnsName(authZone). Do() if err != nil { return "", fmt.Errorf("GoogleCloud API call failed: %v", err) } if len(zones.ManagedZones) == 0 { return "", fmt.Errorf("No matching GoogleCloud domain found for domain %s", authZone) } return zones.ManagedZones[0].Name, nil }
// Present creates a record with a secret func (provider *DNSProvider) Present(domain, token, keyAuth string) error { fqdn, value, _ := acme.DNS01Record(domain, keyAuth) authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) } // 1. Aurora will happily create the TXT record when it is provided a fqdn, // but it will only appear in the control panel and will not be // propagated to DNS servers. Extract and use subdomain instead. // 2. A trailing dot in the fqdn will cause Aurora to add a trailing dot to // the subdomain, resulting in _acme-challenge..<domain> rather // than _acme-challenge.<domain> subdomain := fqdn[0 : len(fqdn)-len(authZone)-1] authZone = acme.UnFqdn(authZone) zoneRecord, err := provider.getZoneInformationByName(authZone) reqData := records.CreateRecordRequest{ RecordType: "TXT", Name: subdomain, Content: value, TTL: 300, } respData, err := provider.client.CreateRecord(zoneRecord.ID, reqData) if err != nil { return fmt.Errorf("Could not create record: '%s'.", err) } provider.recordIDsMu.Lock() provider.recordIDs[fqdn] = respData.ID provider.recordIDsMu.Unlock() return nil }
// Present creates a TXT record using the specified parameters func (d *DNSProvider) Present(domain, token, keyAuth string) error { fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return err } err = d.login() if err != nil { return err } data := map[string]interface{}{ "rdata": map[string]string{ "txtdata": value, }, "ttl": strconv.Itoa(ttl), } resource := fmt.Sprintf("TXTRecord/%s/%s/", authZone, fqdn) _, err = d.sendRequest("POST", resource, data) if err != nil { return err } err = d.publish(authZone, "Added TXT record for ACME dns-01 challenge using lego client") if err != nil { return err } err = d.logout() if err != nil { return err } return nil }
// Present creates a TXT record using the specified parameters func (d *DNSProvider) Present(domain, token, keyAuth string) error { // txtRecordRequest represents the request body to DO's API to make a TXT record type txtRecordRequest struct { RecordType string `json:"type"` Name string `json:"name"` Data string `json:"data"` } // txtRecordResponse represents a response from DO's API after making a TXT record type txtRecordResponse struct { DomainRecord struct { ID int `json:"id"` Type string `json:"type"` Name string `json:"name"` Data string `json:"data"` } `json:"domain_record"` } fqdn, value, _ := acme.DNS01Record(domain, keyAuth) authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) } authZone = acme.UnFqdn(authZone) reqURL := fmt.Sprintf("%s/v2/domains/%s/records", digitalOceanBaseURL, authZone) reqData := txtRecordRequest{RecordType: "TXT", Name: fqdn, Data: value} body, err := json.Marshal(reqData) if err != nil { return err } req, err := http.NewRequest("POST", reqURL, bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", d.apiAuthToken)) client := http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 400 { var errInfo digitalOceanAPIError json.NewDecoder(resp.Body).Decode(&errInfo) return fmt.Errorf("HTTP %d: %s: %s", resp.StatusCode, errInfo.ID, errInfo.Message) } // Everything looks good; but we'll need the ID later to delete the record var respData txtRecordResponse err = json.NewDecoder(resp.Body).Decode(&respData) if err != nil { return err } d.recordIDsMu.Lock() d.recordIDs[fqdn] = respData.DomainRecord.ID d.recordIDsMu.Unlock() return nil }