Skip to content

Commit

Permalink
Merge pull request #710 from ldez/feature/httpreq
Browse files Browse the repository at this point in the history
Add DNS provider for "HTTP request".
  • Loading branch information
ayang64 authored Nov 9, 2018
2 parents e89afae + eb04d86 commit fac6e49
Show file tree
Hide file tree
Showing 5 changed files with 519 additions and 0 deletions.
2 changes: 2 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ Here is an example bash command using the CloudFlare DNS provider:
fmt.Fprintln(w, "\tglesys:\tGLESYS_API_USER, GLESYS_API_KEY")
fmt.Fprintln(w, "\tgodaddy:\tGODADDY_API_KEY, GODADDY_API_SECRET")
fmt.Fprintln(w, "\thostingde:\tHOSTINGDE_API_KEY, HOSTINGDE_ZONE_NAME")
fmt.Fprintln(w, "\thttpreq:\tHTTPREQ_ENDPOINT, HTTPREQ_MODE, HTTPREQ_USERNAME, HTTPREQ_PASSWORD")
fmt.Fprintln(w, "\tiij:\tIIJ_API_ACCESS_KEY, IIJ_API_SECRET_KEY, IIJ_DO_SERVICE_CODE")
fmt.Fprintln(w, "\tinwx:\tINWX_USERNAME, INWX_PASSWORD")
fmt.Fprintln(w, "\tlightsail:\tAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, DNS_ZONE")
Expand Down Expand Up @@ -275,6 +276,7 @@ Here is an example bash command using the CloudFlare DNS provider:
fmt.Fprintln(w, "\tglesys:\tGLESYS_POLLING_INTERVAL, GLESYS_PROPAGATION_TIMEOUT, GLESYS_TTL, GLESYS_HTTP_TIMEOUT")
fmt.Fprintln(w, "\tgodaddy:\tGODADDY_POLLING_INTERVAL, GODADDY_PROPAGATION_TIMEOUT, GODADDY_TTL, GODADDY_HTTP_TIMEOUT")
fmt.Fprintln(w, "\thostingde:\tHOSTINGDE_POLLING_INTERVAL, HOSTINGDE_PROPAGATION_TIMEOUT, HOSTINGDE_TTL, HOSTINGDE_HTTP_TIMEOUT")
fmt.Fprintln(w, "\thttpreq:\t,HTTPREQ_POLLING_INTERVAL, HTTPREQ_PROPAGATION_TIMEOUT, HTTPREQ_HTTP_TIMEOUT")
fmt.Fprintln(w, "\tiij:\tIIJ_POLLING_INTERVAL, IIJ_PROPAGATION_TIMEOUT, IIJ_TTL")
fmt.Fprintln(w, "\tinwx:\tINWX_POLLING_INTERVAL, INWX_PROPAGATION_TIMEOUT, INWX_TTL, INWX_SANDBOX")
fmt.Fprintln(w, "\tlightsail:\tLIGHTSAIL_POLLING_INTERVAL, LIGHTSAIL_PROPAGATION_TIMEOUT")
Expand Down
3 changes: 3 additions & 0 deletions providers/dns/dns_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/xenolf/lego/providers/dns/glesys"
"github.com/xenolf/lego/providers/dns/godaddy"
"github.com/xenolf/lego/providers/dns/hostingde"
"github.com/xenolf/lego/providers/dns/httpreq"
"github.com/xenolf/lego/providers/dns/iij"
"github.com/xenolf/lego/providers/dns/inwx"
"github.com/xenolf/lego/providers/dns/lightsail"
Expand Down Expand Up @@ -105,6 +106,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
return godaddy.NewDNSProvider()
case "hostingde":
return hostingde.NewDNSProvider()
case "httpreq":
return httpreq.NewDNSProvider()
case "iij":
return iij.NewDNSProvider()
case "inwx":
Expand Down
193 changes: 193 additions & 0 deletions providers/dns/httpreq/httpreq.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Package httpreq implements a DNS provider for solving the DNS-01 challenge through a HTTP server.
package httpreq

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"time"

"github.com/xenolf/lego/acme"
"github.com/xenolf/lego/platform/config/env"
)

type message struct {
FQDN string `json:"fqdn"`
Value string `json:"value"`
}

type messageRaw struct {
Domain string `json:"domain"`
Token string `json:"token"`
KeyAuth string `json:"keyAuth"`
}

// Config is used to configure the creation of the DNSProvider
type Config struct {
Endpoint *url.URL
Mode string
Username string
Password string
PropagationTimeout time.Duration
PollingInterval time.Duration
HTTPClient *http.Client
}

// NewDefaultConfig returns a default configuration for the DNSProvider
func NewDefaultConfig() *Config {
return &Config{
PropagationTimeout: env.GetOrDefaultSecond("HTTPREQ_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond("HTTPREQ_POLLING_INTERVAL", acme.DefaultPollingInterval),
HTTPClient: &http.Client{
Timeout: env.GetOrDefaultSecond("HTTPREQ_HTTP_TIMEOUT", 30*time.Second),
},
}
}

// DNSProvider describes a provider for acme-proxy
type DNSProvider struct {
config *Config
}

// NewDNSProvider returns a DNSProvider instance.
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get("HTTPREQ_ENDPOINT")
if err != nil {
return nil, fmt.Errorf("httpreq: %v", err)
}

endpoint, err := url.Parse(values["HTTPREQ_ENDPOINT"])
if err != nil {
return nil, fmt.Errorf("httpreq: %v", err)
}

config := NewDefaultConfig()
config.Mode = os.Getenv("HTTPREQ_MODE")
config.Username = os.Getenv("HTTPREQ_USERNAME")
config.Password = os.Getenv("HTTPREQ_PASSWORD")
config.Endpoint = endpoint
return NewDNSProviderConfig(config)
}

// NewDNSProviderConfig return a DNSProvider .
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("httpreq: the configuration of the DNS provider is nil")
}

if config.Endpoint == nil {
return nil, errors.New("httpreq: the endpoint is missing")
}

return &DNSProvider{config: config}, 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
}

// Present creates a TXT record to fulfill the dns-01 challenge
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
if d.config.Mode == "RAW" {
msg := &messageRaw{
Domain: domain,
Token: token,
KeyAuth: keyAuth,
}

err := d.doPost("/present", msg)
if err != nil {
return fmt.Errorf("httpreq: %v", err)
}
return nil
}

fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
msg := &message{
FQDN: fqdn,
Value: value,
}

err := d.doPost("/present", msg)
if err != nil {
return fmt.Errorf("httpreq: %v", err)
}
return nil
}

// CleanUp removes the TXT record matching the specified parameters
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
if d.config.Mode == "RAW" {
msg := &messageRaw{
Domain: domain,
Token: token,
KeyAuth: keyAuth,
}

err := d.doPost("/cleanup", msg)
if err != nil {
return fmt.Errorf("httpreq: %v", err)
}
return nil
}

fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
msg := &message{
FQDN: fqdn,
Value: value,
}

err := d.doPost("/cleanup", msg)
if err != nil {
return fmt.Errorf("httpreq: %v", err)
}
return nil
}

func (d *DNSProvider) doPost(uri string, msg interface{}) error {
reqBody := &bytes.Buffer{}
err := json.NewEncoder(reqBody).Encode(msg)
if err != nil {
return err
}

endpoint, err := d.config.Endpoint.Parse(uri)
if err != nil {
return err
}

req, err := http.NewRequest(http.MethodPost, endpoint.String(), reqBody)
if err != nil {
return err
}

req.Header.Set("Content-Type", "application/json")

if len(d.config.Username) > 0 && len(d.config.Password) > 0 {
req.SetBasicAuth(d.config.Username, d.config.Password)
}

resp, err := d.config.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode >= http.StatusBadRequest {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("%d: failed to read response body: %v", resp.StatusCode, err)
}

return fmt.Errorf("%d: request failed: %v", resp.StatusCode, string(body))
}

return nil
}
Loading

0 comments on commit fac6e49

Please sign in to comment.