1
0
Fork 0

Cherry pick v1.7 into master

This commit is contained in:
Ludovic Fernandez 2018-11-19 16:40:03 +01:00 committed by Traefiker Bot
parent a09dfa3ce1
commit b6498cdcbc
73 changed files with 6573 additions and 186 deletions

View file

@ -0,0 +1,205 @@
package conoha
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
)
const (
identityBaseURL = "https://identity.%s.conoha.io"
dnsServiceBaseURL = "https://dns-service.%s.conoha.io"
)
// IdentityRequest is an authentication request body.
type IdentityRequest struct {
Auth Auth `json:"auth"`
}
// Auth is an authentication information.
type Auth struct {
TenantID string `json:"tenantId"`
PasswordCredentials PasswordCredentials `json:"passwordCredentials"`
}
// PasswordCredentials is API-user's credentials.
type PasswordCredentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
// IdentityResponse is an authentication response body.
type IdentityResponse struct {
Access Access `json:"access"`
}
// Access is an identity information.
type Access struct {
Token Token `json:"token"`
}
// Token is an api access token.
type Token struct {
ID string `json:"id"`
}
// DomainListResponse is a response of a domain listing request.
type DomainListResponse struct {
Domains []Domain `json:"domains"`
}
// Domain is a hosted domain entry.
type Domain struct {
ID string `json:"id"`
Name string `json:"name"`
}
// RecordListResponse is a response of record listing request.
type RecordListResponse struct {
Records []Record `json:"records"`
}
// Record is a record entry.
type Record struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Type string `json:"type"`
Data string `json:"data"`
TTL int `json:"ttl"`
}
// Client is a ConoHa API client.
type Client struct {
token string
endpoint string
httpClient *http.Client
}
// NewClient returns a client instance logged into the ConoHa service.
func NewClient(region string, auth Auth, httpClient *http.Client) (*Client, error) {
if httpClient == nil {
httpClient = &http.Client{}
}
c := &Client{httpClient: httpClient}
c.endpoint = fmt.Sprintf(identityBaseURL, region)
identity, err := c.getIdentity(auth)
if err != nil {
return nil, fmt.Errorf("failed to login: %v", err)
}
c.token = identity.Access.Token.ID
c.endpoint = fmt.Sprintf(dnsServiceBaseURL, region)
return c, nil
}
func (c *Client) getIdentity(auth Auth) (*IdentityResponse, error) {
req := &IdentityRequest{Auth: auth}
identity := &IdentityResponse{}
err := c.do(http.MethodPost, "/v2.0/tokens", req, identity)
if err != nil {
return nil, err
}
return identity, nil
}
// GetDomainID returns an ID of specified domain.
func (c *Client) GetDomainID(domainName string) (string, error) {
domainList := &DomainListResponse{}
err := c.do(http.MethodGet, "/v1/domains", nil, domainList)
if err != nil {
return "", err
}
for _, domain := range domainList.Domains {
if domain.Name == domainName {
return domain.ID, nil
}
}
return "", fmt.Errorf("no such domain: %s", domainName)
}
// GetRecordID returns an ID of specified record.
func (c *Client) GetRecordID(domainID, recordName, recordType, data string) (string, error) {
recordList := &RecordListResponse{}
err := c.do(http.MethodGet, fmt.Sprintf("/v1/domains/%s/records", domainID), nil, recordList)
if err != nil {
return "", err
}
for _, record := range recordList.Records {
if record.Name == recordName && record.Type == recordType && record.Data == data {
return record.ID, nil
}
}
return "", errors.New("no such record")
}
// CreateRecord adds new record.
func (c *Client) CreateRecord(domainID string, record Record) error {
return c.do(http.MethodPost, fmt.Sprintf("/v1/domains/%s/records", domainID), record, nil)
}
// DeleteRecord removes specified record.
func (c *Client) DeleteRecord(domainID, recordID string) error {
return c.do(http.MethodDelete, fmt.Sprintf("/v1/domains/%s/records/%s", domainID, recordID), nil, nil)
}
func (c *Client) do(method, path string, payload, result interface{}) error {
body := bytes.NewReader(nil)
if payload != nil {
bodyBytes, err := json.Marshal(payload)
if err != nil {
return err
}
body = bytes.NewReader(bodyBytes)
}
req, err := http.NewRequest(method, c.endpoint+path, body)
if err != nil {
return err
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Auth-Token", c.token)
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
defer resp.Body.Close()
return fmt.Errorf("HTTP request failed with status code %d: %s", resp.StatusCode, string(respBody))
}
if result != nil {
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
defer resp.Body.Close()
return json.Unmarshal(respBody, result)
}
return nil
}

View file

@ -0,0 +1,148 @@
// Package conoha implements a DNS provider for solving the DNS-01 challenge
// using ConoHa DNS.
package conoha
import (
"errors"
"fmt"
"net/http"
"time"
"github.com/xenolf/lego/acme"
"github.com/xenolf/lego/platform/config/env"
)
// Config is used to configure the creation of the DNSProvider
type Config struct {
Region string
TenantID string
Username string
Password string
TTL int
PropagationTimeout time.Duration
PollingInterval time.Duration
HTTPClient *http.Client
}
// NewDefaultConfig returns a default configuration for the DNSProvider
func NewDefaultConfig() *Config {
return &Config{
Region: env.GetOrDefaultString("CONOHA_REGION", "tyo1"),
TTL: env.GetOrDefaultInt("CONOHA_TTL", 60),
PropagationTimeout: env.GetOrDefaultSecond("CONOHA_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond("CONOHA_POLLING_INTERVAL", acme.DefaultPollingInterval),
HTTPClient: &http.Client{
Timeout: env.GetOrDefaultSecond("CONOHA_HTTP_TIMEOUT", 30*time.Second),
},
}
}
// DNSProvider is an implementation of the acme.ChallengeProvider interface
type DNSProvider struct {
config *Config
client *Client
}
// NewDNSProvider returns a DNSProvider instance configured for ConoHa DNS.
// Credentials must be passed in the environment variables: CONOHA_TENANT_ID, CONOHA_API_USERNAME, CONOHA_API_PASSWORD
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get("CONOHA_TENANT_ID", "CONOHA_API_USERNAME", "CONOHA_API_PASSWORD")
if err != nil {
return nil, fmt.Errorf("conoha: %v", err)
}
config := NewDefaultConfig()
config.TenantID = values["CONOHA_TENANT_ID"]
config.Username = values["CONOHA_API_USERNAME"]
config.Password = values["CONOHA_API_PASSWORD"]
return NewDNSProviderConfig(config)
}
// NewDNSProviderConfig return a DNSProvider instance configured for ConoHa DNS.
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("conoha: the configuration of the DNS provider is nil")
}
if config.TenantID == "" || config.Username == "" || config.Password == "" {
return nil, errors.New("conoha: some credentials information are missing")
}
auth := Auth{
TenantID: config.TenantID,
PasswordCredentials: PasswordCredentials{
Username: config.Username,
Password: config.Password,
},
}
client, err := NewClient(config.Region, auth, config.HTTPClient)
if err != nil {
return nil, fmt.Errorf("conoha: failed to create client: %v", err)
}
return &DNSProvider{config: config, client: client}, nil
}
// Present creates a TXT record to fulfill the dns-01 challenge.
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil {
return err
}
id, err := d.client.GetDomainID(authZone)
if err != nil {
return fmt.Errorf("conoha: failed to get domain ID: %v", err)
}
record := Record{
Name: fqdn,
Type: "TXT",
Data: value,
TTL: d.config.TTL,
}
err = d.client.CreateRecord(id, record)
if err != nil {
return fmt.Errorf("conoha: failed to create record: %v", err)
}
return nil
}
// CleanUp clears ConoHa DNS TXT record
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil {
return err
}
domID, err := d.client.GetDomainID(authZone)
if err != nil {
return fmt.Errorf("conoha: failed to get domain ID: %v", err)
}
recID, err := d.client.GetRecordID(domID, fqdn, "TXT", value)
if err != nil {
return fmt.Errorf("conoha: failed to get record ID: %v", err)
}
err = d.client.DeleteRecord(domID, recID)
if err != nil {
return fmt.Errorf("conoha: failed to delete record: %v", err)
}
return nil
}
// Timeout returns the timeout and interval to use when checking for DNS propagation.
// Adjusting here to cope with spikes in propagation times.
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}