lego/providers/dns/hostingde/client.go
Ludovic Fernandez 42941ccea6
Refactor the core of the lib (#700)
- Packages
- Isolate code used by the CLI into the package `cmd`
- (experimental) Add e2e tests for HTTP01, TLS-ALPN-01 and DNS-01, use [Pebble](https://github.com/letsencrypt/pebble) and [challtestsrv](https://github.com/letsencrypt/boulder/tree/master/test/challtestsrv) 
- Support non-ascii domain name (punnycode)
- Check all challenges in a predictable order
- No more global exported variables
- Archive revoked certificates
- Fixes revocation for subdomains and non-ascii domains
- Disable pending authorizations
- use pointer for RemoteError/ProblemDetails
- Poll authz URL instead of challenge URL
- The ability for a DNS provider to solve the challenge sequentially
- Check all nameservers in a predictable order
- Option to disable the complete propagation Requirement
- CLI, support for renew with CSR
- CLI, add SAN on renew
- Add command to list certificates.
- Logs every iteration of waiting for the propagation
- update DNSimple client
- update github.com/miekg/dns
2018-12-06 22:50:17 +01:00

143 lines
4.5 KiB
Go

package hostingde
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
)
const defaultBaseURL = "https://secure.hosting.de/api/dns/v1/json"
// RecordsAddRequest represents a DNS record to add
type RecordsAddRequest struct {
Name string `json:"name"`
Type string `json:"type"`
Content string `json:"content"`
TTL int `json:"ttl"`
}
// RecordsDeleteRequest represents a DNS record to remove
type RecordsDeleteRequest struct {
Name string `json:"name"`
Type string `json:"type"`
Content string `json:"content"`
ID string `json:"id"`
}
// ZoneConfigObject represents the ZoneConfig-section of a hosting.de API response.
type ZoneConfigObject struct {
AccountID string `json:"accountId"`
EmailAddress string `json:"emailAddress"`
ID string `json:"id"`
LastChangeDate string `json:"lastChangeDate"`
MasterIP string `json:"masterIp"`
Name string `json:"name"`
NameUnicode string `json:"nameUnicode"`
SOAValues struct {
Expire int `json:"expire"`
NegativeTTL int `json:"negativeTtl"`
Refresh int `json:"refresh"`
Retry int `json:"retry"`
Serial string `json:"serial"`
TTL int `json:"ttl"`
} `json:"soaValues"`
Status string `json:"status"`
TemplateValues string `json:"templateValues"`
Type string `json:"type"`
ZoneTransferWhitelist []string `json:"zoneTransferWhitelist"`
}
// ZoneUpdateError represents an error in a ZoneUpdateResponse
type ZoneUpdateError struct {
Code int `json:"code"`
ContextObject string `json:"contextObject"`
ContextPath string `json:"contextPath"`
Details []string `json:"details"`
Text string `json:"text"`
Value string `json:"value"`
}
// ZoneUpdateMetadata represents the metadata in a ZoneUpdateResponse
type ZoneUpdateMetadata struct {
ClientTransactionID string `json:"clientTransactionId"`
ServerTransactionID string `json:"serverTransactionId"`
}
// ZoneUpdateResponse represents a response from hosting.de API
type ZoneUpdateResponse struct {
Errors []ZoneUpdateError `json:"errors"`
Metadata ZoneUpdateMetadata `json:"metadata"`
Warnings []string `json:"warnings"`
Status string `json:"status"`
Response struct {
Records []struct {
Content string `json:"content"`
Type string `json:"type"`
ID string `json:"id"`
Name string `json:"name"`
LastChangeDate string `json:"lastChangeDate"`
Priority int `json:"priority"`
RecordTemplateID string `json:"recordTemplateId"`
ZoneConfigID string `json:"zoneConfigId"`
TTL int `json:"ttl"`
} `json:"records"`
ZoneConfig ZoneConfigObject `json:"zoneConfig"`
} `json:"response"`
}
// ZoneConfigSelector represents a "minimal" ZoneConfig object used in hosting.de API requests
type ZoneConfigSelector struct {
Name string `json:"name"`
}
// ZoneUpdateRequest represents a hosting.de API ZoneUpdate request
type ZoneUpdateRequest struct {
AuthToken string `json:"authToken"`
ZoneConfigSelector `json:"zoneConfig"`
RecordsToAdd []RecordsAddRequest `json:"recordsToAdd"`
RecordsToDelete []RecordsDeleteRequest `json:"recordsToDelete"`
}
func (d *DNSProvider) updateZone(updateRequest ZoneUpdateRequest) (*ZoneUpdateResponse, error) {
body, err := json.Marshal(updateRequest)
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, defaultBaseURL+"/zoneUpdate", bytes.NewReader(body))
if err != nil {
return nil, err
}
resp, err := d.config.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("error querying API: %v", err)
}
defer resp.Body.Close()
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.New(toUnreadableBodyMessage(req, content))
}
// Everything looks good; but we'll need the ID later to delete the record
updateResponse := &ZoneUpdateResponse{}
err = json.Unmarshal(content, updateResponse)
if err != nil {
return nil, fmt.Errorf("%v: %s", err, toUnreadableBodyMessage(req, content))
}
if updateResponse.Status != "success" && updateResponse.Status != "pending" {
return updateResponse, errors.New(toUnreadableBodyMessage(req, content))
}
return updateResponse, nil
}
func toUnreadableBodyMessage(req *http.Request, rawBody []byte) string {
return fmt.Sprintf("the request %s sent a response with a body which is an invalid format: %q", req.URL, string(rawBody))
}