Skip to content

Commit fac6e49

Browse files
authored
Merge pull request #710 from ldez/feature/httpreq
Add DNS provider for "HTTP request".
2 parents e89afae + eb04d86 commit fac6e49

File tree

5 files changed

+519
-0
lines changed

5 files changed

+519
-0
lines changed

cli.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ Here is an example bash command using the CloudFlare DNS provider:
226226
fmt.Fprintln(w, "\tglesys:\tGLESYS_API_USER, GLESYS_API_KEY")
227227
fmt.Fprintln(w, "\tgodaddy:\tGODADDY_API_KEY, GODADDY_API_SECRET")
228228
fmt.Fprintln(w, "\thostingde:\tHOSTINGDE_API_KEY, HOSTINGDE_ZONE_NAME")
229+
fmt.Fprintln(w, "\thttpreq:\tHTTPREQ_ENDPOINT, HTTPREQ_MODE, HTTPREQ_USERNAME, HTTPREQ_PASSWORD")
229230
fmt.Fprintln(w, "\tiij:\tIIJ_API_ACCESS_KEY, IIJ_API_SECRET_KEY, IIJ_DO_SERVICE_CODE")
230231
fmt.Fprintln(w, "\tinwx:\tINWX_USERNAME, INWX_PASSWORD")
231232
fmt.Fprintln(w, "\tlightsail:\tAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, DNS_ZONE")
@@ -275,6 +276,7 @@ Here is an example bash command using the CloudFlare DNS provider:
275276
fmt.Fprintln(w, "\tglesys:\tGLESYS_POLLING_INTERVAL, GLESYS_PROPAGATION_TIMEOUT, GLESYS_TTL, GLESYS_HTTP_TIMEOUT")
276277
fmt.Fprintln(w, "\tgodaddy:\tGODADDY_POLLING_INTERVAL, GODADDY_PROPAGATION_TIMEOUT, GODADDY_TTL, GODADDY_HTTP_TIMEOUT")
277278
fmt.Fprintln(w, "\thostingde:\tHOSTINGDE_POLLING_INTERVAL, HOSTINGDE_PROPAGATION_TIMEOUT, HOSTINGDE_TTL, HOSTINGDE_HTTP_TIMEOUT")
279+
fmt.Fprintln(w, "\thttpreq:\t,HTTPREQ_POLLING_INTERVAL, HTTPREQ_PROPAGATION_TIMEOUT, HTTPREQ_HTTP_TIMEOUT")
278280
fmt.Fprintln(w, "\tiij:\tIIJ_POLLING_INTERVAL, IIJ_PROPAGATION_TIMEOUT, IIJ_TTL")
279281
fmt.Fprintln(w, "\tinwx:\tINWX_POLLING_INTERVAL, INWX_PROPAGATION_TIMEOUT, INWX_TTL, INWX_SANDBOX")
280282
fmt.Fprintln(w, "\tlightsail:\tLIGHTSAIL_POLLING_INTERVAL, LIGHTSAIL_PROPAGATION_TIMEOUT")

providers/dns/dns_providers.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/xenolf/lego/providers/dns/glesys"
2929
"github.com/xenolf/lego/providers/dns/godaddy"
3030
"github.com/xenolf/lego/providers/dns/hostingde"
31+
"github.com/xenolf/lego/providers/dns/httpreq"
3132
"github.com/xenolf/lego/providers/dns/iij"
3233
"github.com/xenolf/lego/providers/dns/inwx"
3334
"github.com/xenolf/lego/providers/dns/lightsail"
@@ -105,6 +106,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
105106
return godaddy.NewDNSProvider()
106107
case "hostingde":
107108
return hostingde.NewDNSProvider()
109+
case "httpreq":
110+
return httpreq.NewDNSProvider()
108111
case "iij":
109112
return iij.NewDNSProvider()
110113
case "inwx":

providers/dns/httpreq/httpreq.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Package httpreq implements a DNS provider for solving the DNS-01 challenge through a HTTP server.
2+
package httpreq
3+
4+
import (
5+
"bytes"
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
"io/ioutil"
10+
"net/http"
11+
"net/url"
12+
"os"
13+
"time"
14+
15+
"github.com/xenolf/lego/acme"
16+
"github.com/xenolf/lego/platform/config/env"
17+
)
18+
19+
type message struct {
20+
FQDN string `json:"fqdn"`
21+
Value string `json:"value"`
22+
}
23+
24+
type messageRaw struct {
25+
Domain string `json:"domain"`
26+
Token string `json:"token"`
27+
KeyAuth string `json:"keyAuth"`
28+
}
29+
30+
// Config is used to configure the creation of the DNSProvider
31+
type Config struct {
32+
Endpoint *url.URL
33+
Mode string
34+
Username string
35+
Password string
36+
PropagationTimeout time.Duration
37+
PollingInterval time.Duration
38+
HTTPClient *http.Client
39+
}
40+
41+
// NewDefaultConfig returns a default configuration for the DNSProvider
42+
func NewDefaultConfig() *Config {
43+
return &Config{
44+
PropagationTimeout: env.GetOrDefaultSecond("HTTPREQ_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
45+
PollingInterval: env.GetOrDefaultSecond("HTTPREQ_POLLING_INTERVAL", acme.DefaultPollingInterval),
46+
HTTPClient: &http.Client{
47+
Timeout: env.GetOrDefaultSecond("HTTPREQ_HTTP_TIMEOUT", 30*time.Second),
48+
},
49+
}
50+
}
51+
52+
// DNSProvider describes a provider for acme-proxy
53+
type DNSProvider struct {
54+
config *Config
55+
}
56+
57+
// NewDNSProvider returns a DNSProvider instance.
58+
func NewDNSProvider() (*DNSProvider, error) {
59+
values, err := env.Get("HTTPREQ_ENDPOINT")
60+
if err != nil {
61+
return nil, fmt.Errorf("httpreq: %v", err)
62+
}
63+
64+
endpoint, err := url.Parse(values["HTTPREQ_ENDPOINT"])
65+
if err != nil {
66+
return nil, fmt.Errorf("httpreq: %v", err)
67+
}
68+
69+
config := NewDefaultConfig()
70+
config.Mode = os.Getenv("HTTPREQ_MODE")
71+
config.Username = os.Getenv("HTTPREQ_USERNAME")
72+
config.Password = os.Getenv("HTTPREQ_PASSWORD")
73+
config.Endpoint = endpoint
74+
return NewDNSProviderConfig(config)
75+
}
76+
77+
// NewDNSProviderConfig return a DNSProvider .
78+
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
79+
if config == nil {
80+
return nil, errors.New("httpreq: the configuration of the DNS provider is nil")
81+
}
82+
83+
if config.Endpoint == nil {
84+
return nil, errors.New("httpreq: the endpoint is missing")
85+
}
86+
87+
return &DNSProvider{config: config}, nil
88+
}
89+
90+
// Timeout returns the timeout and interval to use when checking for DNS propagation.
91+
// Adjusting here to cope with spikes in propagation times.
92+
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
93+
return d.config.PropagationTimeout, d.config.PollingInterval
94+
}
95+
96+
// Present creates a TXT record to fulfill the dns-01 challenge
97+
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
98+
if d.config.Mode == "RAW" {
99+
msg := &messageRaw{
100+
Domain: domain,
101+
Token: token,
102+
KeyAuth: keyAuth,
103+
}
104+
105+
err := d.doPost("/present", msg)
106+
if err != nil {
107+
return fmt.Errorf("httpreq: %v", err)
108+
}
109+
return nil
110+
}
111+
112+
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
113+
msg := &message{
114+
FQDN: fqdn,
115+
Value: value,
116+
}
117+
118+
err := d.doPost("/present", msg)
119+
if err != nil {
120+
return fmt.Errorf("httpreq: %v", err)
121+
}
122+
return nil
123+
}
124+
125+
// CleanUp removes the TXT record matching the specified parameters
126+
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
127+
if d.config.Mode == "RAW" {
128+
msg := &messageRaw{
129+
Domain: domain,
130+
Token: token,
131+
KeyAuth: keyAuth,
132+
}
133+
134+
err := d.doPost("/cleanup", msg)
135+
if err != nil {
136+
return fmt.Errorf("httpreq: %v", err)
137+
}
138+
return nil
139+
}
140+
141+
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
142+
msg := &message{
143+
FQDN: fqdn,
144+
Value: value,
145+
}
146+
147+
err := d.doPost("/cleanup", msg)
148+
if err != nil {
149+
return fmt.Errorf("httpreq: %v", err)
150+
}
151+
return nil
152+
}
153+
154+
func (d *DNSProvider) doPost(uri string, msg interface{}) error {
155+
reqBody := &bytes.Buffer{}
156+
err := json.NewEncoder(reqBody).Encode(msg)
157+
if err != nil {
158+
return err
159+
}
160+
161+
endpoint, err := d.config.Endpoint.Parse(uri)
162+
if err != nil {
163+
return err
164+
}
165+
166+
req, err := http.NewRequest(http.MethodPost, endpoint.String(), reqBody)
167+
if err != nil {
168+
return err
169+
}
170+
171+
req.Header.Set("Content-Type", "application/json")
172+
173+
if len(d.config.Username) > 0 && len(d.config.Password) > 0 {
174+
req.SetBasicAuth(d.config.Username, d.config.Password)
175+
}
176+
177+
resp, err := d.config.HTTPClient.Do(req)
178+
if err != nil {
179+
return err
180+
}
181+
defer resp.Body.Close()
182+
183+
if resp.StatusCode >= http.StatusBadRequest {
184+
body, err := ioutil.ReadAll(resp.Body)
185+
if err != nil {
186+
return fmt.Errorf("%d: failed to read response body: %v", resp.StatusCode, err)
187+
}
188+
189+
return fmt.Errorf("%d: request failed: %v", resp.StatusCode, string(body))
190+
}
191+
192+
return nil
193+
}

0 commit comments

Comments
 (0)