lego/providers/dns/nifcloud/internal/client.go

185 lines
4.1 KiB
Go
Raw Normal View History

2019-03-11 16:56:48 +00:00
package internal
import (
"bytes"
2023-05-05 07:49:38 +00:00
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/xml"
"errors"
"fmt"
2023-05-05 07:49:38 +00:00
"io"
"net/http"
2023-05-05 07:49:38 +00:00
"net/url"
"time"
2023-05-05 07:49:38 +00:00
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
)
const (
2019-01-22 07:34:45 +00:00
defaultBaseURL = "https://dns.api.nifcloud.com"
apiVersion = "2012-12-12N2013-12-16"
2020-08-09 14:39:44 +00:00
// XMLNs XML NS of Route53.
XMLNs = "https://route53.amazonaws.com/doc/2012-12-12/"
)
2023-05-05 07:49:38 +00:00
// Client the API client for NIFCLOUD DNS.
type Client struct {
accessKey string
secretKey string
2023-05-05 07:49:38 +00:00
BaseURL *url.URL
HTTPClient *http.Client
}
2020-05-08 17:35:25 +00:00
// NewClient Creates a new client of NIFCLOUD DNS.
2020-07-09 23:48:18 +00:00
func NewClient(accessKey, secretKey string) (*Client, error) {
2021-03-04 19:16:59 +00:00
if accessKey == "" || secretKey == "" {
return nil, errors.New("credentials missing")
}
2023-05-05 07:49:38 +00:00
baseURL, _ := url.Parse(defaultBaseURL)
return &Client{
accessKey: accessKey,
secretKey: secretKey,
2023-05-05 07:49:38 +00:00
BaseURL: baseURL,
HTTPClient: &http.Client{Timeout: 10 * time.Second},
}, nil
}
// ChangeResourceRecordSets Call ChangeResourceRecordSets API and return response.
2023-05-05 07:49:38 +00:00
func (c *Client) ChangeResourceRecordSets(ctx context.Context, hostedZoneID string, input ChangeResourceRecordSetsRequest) (*ChangeResourceRecordSetsResponse, error) {
endpoint := c.BaseURL.JoinPath(apiVersion, "hostedzone", hostedZoneID, "rrset")
2023-05-05 07:49:38 +00:00
req, err := newXMLRequest(ctx, http.MethodPost, endpoint, input)
if err != nil {
return nil, err
}
2023-05-05 07:49:38 +00:00
output := &ChangeResourceRecordSetsResponse{}
err = c.do(req, output)
if err != nil {
return nil, err
}
2023-05-05 07:49:38 +00:00
return output, nil
}
2023-05-05 07:49:38 +00:00
// GetChange Call GetChange API and return response.
func (c *Client) GetChange(ctx context.Context, statusID string) (*GetChangeResponse, error) {
endpoint := c.BaseURL.JoinPath(apiVersion, "change", statusID)
2023-05-05 07:49:38 +00:00
req, err := newXMLRequest(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return nil, err
}
2023-05-05 07:49:38 +00:00
output := &GetChangeResponse{}
err = c.do(req, output)
if err != nil {
2023-05-05 07:49:38 +00:00
return nil, err
}
2023-05-05 07:49:38 +00:00
return output, nil
}
2023-05-05 07:49:38 +00:00
func (c *Client) do(req *http.Request, result any) error {
err := c.sign(req)
if err != nil {
2023-05-05 07:49:38 +00:00
return fmt.Errorf("an error occurred during the creation of the signature: %w", err)
}
2023-05-05 07:49:38 +00:00
resp, err := c.HTTPClient.Do(req)
if err != nil {
2023-05-05 07:49:38 +00:00
return errutils.NewHTTPDoError(req, err)
}
2023-05-05 07:49:38 +00:00
defer func() { _ = resp.Body.Close() }()
2023-05-05 07:49:38 +00:00
if resp.StatusCode != http.StatusOK {
return parseError(req, resp)
}
2023-05-05 07:49:38 +00:00
if result == nil {
return nil
}
2023-05-05 07:49:38 +00:00
raw, err := io.ReadAll(resp.Body)
if err != nil {
return errutils.NewReadResponseError(req, resp.StatusCode, err)
}
2023-05-05 07:49:38 +00:00
err = xml.Unmarshal(raw, result)
if err != nil {
2023-05-05 07:49:38 +00:00
return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
}
2023-05-05 07:49:38 +00:00
return nil
}
func (c *Client) sign(req *http.Request) error {
if req.Header.Get("Date") == "" {
location, err := time.LoadLocation("GMT")
if err != nil {
return err
}
req.Header.Set("Date", time.Now().In(location).Format(time.RFC1123))
}
if req.URL.Path == "" {
req.URL.Path += "/"
}
mac := hmac.New(sha1.New, []byte(c.secretKey))
_, err := mac.Write([]byte(req.Header.Get("Date")))
if err != nil {
return err
}
hashed := mac.Sum(nil)
signature := base64.StdEncoding.EncodeToString(hashed)
auth := fmt.Sprintf("NIFTY3-HTTPS NiftyAccessKeyId=%s,Algorithm=HmacSHA1,Signature=%s", c.accessKey, signature)
req.Header.Set("X-Nifty-Authorization", auth)
return nil
}
2023-05-05 07:49:38 +00:00
func newXMLRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
2023-10-18 09:53:04 +00:00
body := new(bytes.Buffer)
2023-05-05 07:49:38 +00:00
if payload != nil {
body.WriteString(xml.Header)
err := xml.NewEncoder(body).Encode(payload)
if err != nil {
return nil, fmt.Errorf("failed to create request XML body: %w", err)
}
}
2023-10-18 09:53:04 +00:00
req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), body)
2023-05-05 07:49:38 +00:00
if err != nil {
return nil, fmt.Errorf("unable to create request: %w", err)
}
if payload != nil {
req.Header.Set("Content-Type", "text/xml; charset=utf-8")
}
return req, nil
}
func parseError(req *http.Request, resp *http.Response) error {
raw, _ := io.ReadAll(resp.Body)
errResp := &ErrorResponse{}
err := xml.Unmarshal(raw, errResp)
if err != nil {
return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
}
return errResp.Error
}