42941ccea6
- 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
143 lines
4.5 KiB
Go
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))
|
|
}
|