Skip to content

Commit aa3e7e1

Browse files
authored
Migrate over to certmagic from using autocert (#190)
* Use certmagic for challenge validation * WIP * Get the correct key * Override preflight check logic * Fix logging for imported packages and tidy config.cfg * Fix test and add docstrings * Update README
1 parent af542b4 commit aa3e7e1

8 files changed

+465
-44
lines changed

README.md

+13-11
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ $ dig -t txt @auth.example.org d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example
238238
# DNS interface. Note that systemd-resolved may reserve port 53 on 127.0.0.53
239239
# In this case acme-dns will error out and you will need to define the listening interface
240240
# for example: listen = "127.0.0.1:53"
241-
listen = ":53"
241+
listen = "127.0.0.1:53"
242242
# protocol, "both", "both4", "both6", "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6"
243243
protocol = "both"
244244
# domain name to serve the requests off of
@@ -249,7 +249,7 @@ nsname = "auth.example.org"
249249
nsadmin = "admin.example.org"
250250
# predefined records served in addition to the TXT
251251
records = [
252-
# domain pointing to the public IP of your acme-dns server
252+
# domain pointing to the public IP of your acme-dns server
253253
"auth.example.org. A 198.51.100.1",
254254
# specify that auth.example.org will resolve any *.auth.example.org records
255255
"auth.example.org. NS auth.example.org.",
@@ -261,22 +261,19 @@ debug = false
261261
# Database engine to use, sqlite3 or postgres
262262
engine = "sqlite3"
263263
# Connection string, filename for sqlite3 and postgres://$username:$password@$host/$db_name for postgres
264+
# Please note that the default Docker image uses path /var/lib/acme-dns/acme-dns.db for sqlite3
264265
connection = "/var/lib/acme-dns/acme-dns.db"
265266
# connection = "postgres://user:password@localhost/acmedns_db"
266267

267268
[api]
268-
# domain name to listen requests for, mandatory if using tls = "letsencrypt"
269-
api_domain = ""
269+
# listen ip eg. 127.0.0.1
270+
ip = "0.0.0.0"
270271
# disable registration endpoint
271272
disable_registration = false
272-
# autocert HTTP port, eg. 80 for answering Let's Encrypt HTTP-01 challenges. Mandatory if using tls = "letsencrypt".
273-
autocert_port = "80"
274-
# listen ip, default "" listens on all interfaces/addresses
275-
ip = "127.0.0.1"
276273
# listen port, eg. 443 for default HTTPS
277-
port = "8080"
278-
# possible values: "letsencrypt", "cert", "none"
279-
tls = "none"
274+
port = "443"
275+
# possible values: "letsencrypt", "letsencryptstaging", "cert", "none"
276+
tls = "letsencryptstaging"
280277
# only used if tls = "cert"
281278
tls_cert_privkey = "/etc/tls/example.org/privkey.pem"
282279
tls_cert_fullchain = "/etc/tls/example.org/fullchain.pem"
@@ -346,8 +343,13 @@ use for the renewal.
346343
## Changelog
347344

348345
- v0.8
346+
- NOTE: configuration option: "api_domain" deprecated!
347+
- New
348+
- Automatic HTTP API certificate provisioning using DNS challenges making acme-dns able to acquire certificates even with HTTP api not being accessible from public internet.
349+
- Configuration value for "tls": "letsencryptstaging". Setting it will help you to debug possible issues with HTTP API certificate acquiring process. This is the new default value.
349350
- Changed
350351
- Fixed: EDNS0 support
352+
- Migrated from autocert to [certmagic](https://github.com/mholt/certmagic) for HTTP API certificate handling
351353
- v0.7.2
352354
- Changed
353355
- Fixed: Regression error of not being able to answer to incoming random-case requests.

challengeprovider.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import "github.com/go-acme/lego/challenge/dns01"
4+
5+
// ChallengeProvider implements go-acme/lego Provider interface which is used for ACME DNS challenge handling
6+
type ChallengeProvider struct {
7+
servers []*DNSServer
8+
}
9+
10+
// NewChallengeProvider creates a new instance of ChallengeProvider
11+
func NewChallengeProvider(servers []*DNSServer) ChallengeProvider {
12+
return ChallengeProvider{servers: servers}
13+
}
14+
15+
// Present is used for making the ACME DNS challenge token available for DNS
16+
func (c *ChallengeProvider) Present(_, _, keyAuth string) error {
17+
_, token := dns01.GetRecord("whatever", keyAuth)
18+
for _, s := range c.servers {
19+
s.PersonalKeyAuth = token
20+
}
21+
return nil
22+
}
23+
24+
// CleanUp is called after the run to remove the ACME DNS challenge tokens from DNS records
25+
func (c *ChallengeProvider) CleanUp(_, _, _ string) error {
26+
for _, s := range c.servers {
27+
s.PersonalKeyAuth = ""
28+
}
29+
return nil
30+
}

config.cfg

+3-7
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,14 @@ connection = "/var/lib/acme-dns/acme-dns.db"
3030
# connection = "postgres://user:password@localhost/acmedns_db"
3131

3232
[api]
33-
# domain name to listen requests for, mandatory if using tls = "letsencrypt"
34-
api_domain = ""
3533
# listen ip eg. 127.0.0.1
3634
ip = "0.0.0.0"
3735
# disable registration endpoint
3836
disable_registration = false
39-
# autocert HTTP port, eg. 80 for answering Let's Encrypt HTTP-01 challenges. Mandatory if using tls = "letsencrypt".
40-
autocert_port = "80"
4137
# listen port, eg. 443 for default HTTPS
42-
port = "80"
43-
# possible values: "letsencrypt", "cert", "none"
44-
tls = "none"
38+
port = "443"
39+
# possible values: "letsencrypt", "letsencryptstaging", "cert", "none"
40+
tls = "letsencryptstaging"
4541
# only used if tls = "cert"
4642
tls_cert_privkey = "/etc/tls/example.org/privkey.pem"
4743
tls_cert_fullchain = "/etc/tls/example.org/fullchain.pem"

dns.go

+48-7
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,24 @@ type Records struct {
1515

1616
// DNSServer is the main struct for acme-dns DNS server
1717
type DNSServer struct {
18-
DB database
19-
Server *dns.Server
20-
SOA dns.RR
21-
Domains map[string]Records
18+
DB database
19+
Domain string
20+
Server *dns.Server
21+
SOA dns.RR
22+
PersonalKeyAuth string
23+
Domains map[string]Records
2224
}
2325

2426
// NewDNSServer parses the DNS records from config and returns a new DNSServer struct
25-
func NewDNSServer(db database, addr string, proto string) *DNSServer {
27+
func NewDNSServer(db database, addr string, proto string, domain string) *DNSServer {
2628
var server DNSServer
2729
server.Server = &dns.Server{Addr: addr, Net: proto}
30+
if !strings.HasSuffix(domain, ".") {
31+
domain = domain + "."
32+
}
33+
server.Domain = strings.ToLower(domain)
2834
server.DB = db
35+
server.PersonalKeyAuth = ""
2936
server.Domains = make(map[string]Records)
3037
return &server
3138
}
@@ -148,6 +155,9 @@ func (d *DNSServer) getRecord(q dns.Question) ([]dns.RR, error) {
148155

149156
// answeringForDomain checks if we have any records for a domain
150157
func (d *DNSServer) answeringForDomain(name string) bool {
158+
if d.Domain == strings.ToLower(name) {
159+
return true
160+
}
151161
_, ok := d.Domains[strings.ToLower(name)]
152162
return ok
153163
}
@@ -165,15 +175,38 @@ func (d *DNSServer) isAuthoritative(q dns.Question) bool {
165175
return false
166176
}
167177

178+
// isOwnChallenge checks if the query is for the domain of this acme-dns instance. Used for answering its own ACME challenges
179+
func (d *DNSServer) isOwnChallenge(name string) bool {
180+
domainParts := strings.SplitN(name, ".", 2)
181+
if len(domainParts) == 2 {
182+
if strings.ToLower(domainParts[0]) == "_acme-challenge" {
183+
domain := strings.ToLower(domainParts[1])
184+
if !strings.HasSuffix(domain, ".") {
185+
domain = domain + "."
186+
}
187+
if domain == d.Domain {
188+
return true
189+
}
190+
}
191+
}
192+
return false
193+
}
194+
168195
func (d *DNSServer) answer(q dns.Question) ([]dns.RR, int, bool, error) {
169196
var rcode int
197+
var err error
198+
var txtRRs []dns.RR
170199
var authoritative = d.isAuthoritative(q)
171-
if !d.answeringForDomain(q.Name) {
200+
if !d.isOwnChallenge(q.Name) && !d.answeringForDomain(q.Name) {
172201
rcode = dns.RcodeNameError
173202
}
174203
r, _ := d.getRecord(q)
175204
if q.Qtype == dns.TypeTXT {
176-
txtRRs, err := d.answerTXT(q)
205+
if d.isOwnChallenge(q.Name) {
206+
txtRRs, err = d.answerOwnChallenge(q)
207+
} else {
208+
txtRRs, err = d.answerTXT(q)
209+
}
177210
if err == nil {
178211
for _, txtRR := range txtRRs {
179212
r = append(r, txtRR)
@@ -206,3 +239,11 @@ func (d *DNSServer) answerTXT(q dns.Question) ([]dns.RR, error) {
206239
}
207240
return ra, nil
208241
}
242+
243+
// answerOwnChallenge answers to ACME challenge for acme-dns own certificate
244+
func (d *DNSServer) answerOwnChallenge(q dns.Question) ([]dns.RR, error) {
245+
r := new(dns.TXT)
246+
r.Hdr = dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 1}
247+
r.Txt = append(r.Txt, d.PersonalKeyAuth)
248+
return []dns.RR{r}, nil
249+
}

go.mod

+8-3
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,21 @@ require (
66
github.com/BurntSushi/toml v0.3.1
77
github.com/DATA-DOG/go-sqlmock v1.3.3
88
github.com/ajg/form v1.5.1 // indirect
9+
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
910
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5
10-
github.com/fatih/structs v1.1.0 // indirect
11+
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect
1112
github.com/gavv/httpexpect v2.0.0+incompatible
12-
github.com/google/go-querystring v1.0.0 // indirect
13+
github.com/go-acme/lego v2.7.2+incompatible
14+
github.com/go-acme/lego/v3 v3.1.0
1315
github.com/google/uuid v1.1.1
1416
github.com/gorilla/websocket v1.4.1 // indirect
1517
github.com/imkira/go-interpol v1.1.0 // indirect
1618
github.com/julienschmidt/httprouter v1.3.0
17-
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
19+
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
1820
github.com/lib/pq v1.2.0
21+
github.com/mattn/go-colorable v0.1.4 // indirect
1922
github.com/mattn/go-sqlite3 v1.11.0
23+
github.com/mholt/certmagic v0.8.1-0.20191019173955-6f9f0e6dd0e8
2024
github.com/miekg/dns v1.1.22
2125
github.com/moul/http2curl v1.0.0 // indirect
2226
github.com/rs/cors v1.7.0
@@ -27,6 +31,7 @@ require (
2731
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect
2832
github.com/yudai/gojsondiff v1.0.0 // indirect
2933
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
34+
github.com/yudai/pp v2.0.1+incompatible // indirect
3035
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
3136
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect
3237
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect

0 commit comments

Comments
 (0)