-
Notifications
You must be signed in to change notification settings - Fork 21
/
challenge-servers.go
218 lines (190 loc) · 7.21 KB
/
challenge-servers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
// Package challtestsrv provides a trivially insecure acme challenge response
// server for rapidly testing HTTP-01, DNS-01 and TLS-ALPN-01 challenge types.
package challtestsrv
import (
"fmt"
"log"
"net/http"
"os"
"strings"
"sync"
)
const (
// Default to using localhost for both A and AAAA queries that don't match
// more specific mock host data.
defaultIPv4 = "127.0.0.1"
defaultIPv6 = "::1"
)
// challengeServers offer common functionality to start up and shutdown.
type challengeServer interface {
ListenAndServe() error
Shutdown() error
}
// ChallSrv is a multi-purpose challenge server. Each ChallSrv may have one or
// more ACME challenges it provides servers for. It is safe to use concurrently.
type ChallSrv struct {
log *log.Logger
// servers are the individual challenge server listeners started in New() and
// closed in Shutdown().
servers []challengeServer
// challMu is a RWMutex used to control concurrent updates to the challenge
// response data maps below.
challMu sync.RWMutex
// requestHistory is a map from hostname to a map of event type to a list of
// sequential request events
requestHistory map[string]map[RequestEventType][]RequestEvent
// httpOne is a map of token values to key authorizations used for HTTP-01
// responses.
httpOne map[string]string
// dnsOne is a map of DNS host values to key authorizations used for DNS-01
// responses.
dnsOne map[string][]string
// dnsMocks holds mock DNS data used to respond to DNS queries other than
// DNS-01 TXT challenge lookups.
dnsMocks mockDNSData
// tlsALPNOne is a map of token values to key authorizations used for TLS-ALPN-01
// responses.
tlsALPNOne map[string]string
// redirects is a map of paths to URLs. HTTP challenge servers respond to
// requests for these paths with a 301 to the corresponding URL.
redirects map[string]string
}
// mockDNSData holds mock responses for DNS A, AAAA, and CAA lookups.
type mockDNSData struct {
// The IPv4 address used for all A record responses that don't match a host in
// aRecords.
defaultIPv4 string
// The IPv6 address used for all AAAA record responses that don't match a host
// in aaaaRecords.
defaultIPv6 string
// A map of host to IPv4 addresses in string form for A record responses.
aRecords map[string][]string
// A map of host to IPv6 addresses in string form for AAAA record responses.
aaaaRecords map[string][]string
// A map of host to CAA policies for CAA responses.
caaRecords map[string][]MockCAAPolicy
// A map of host to CNAME records.
cnameRecords map[string]string
// A map of hostnames that should receive a SERVFAIL response for all queries.
servFailRecords map[string]bool
}
// MockCAAPolicy holds a tag and a value for a CAA record. See
// https://tools.ietf.org/html/rfc6844
type MockCAAPolicy struct {
Tag string
Value string
}
// Config holds challenge server configuration
type Config struct {
Log *log.Logger
// HTTPOneAddrs are the HTTP-01 challenge server bind addresses/ports
HTTPOneAddrs []string
// HTTPSOneAddrs are the HTTPS HTTP-01 challenge server bind addresses/ports
HTTPSOneAddrs []string
// DOHAddrs are the DOH challenge server bind addresses/ports
DOHAddrs []string
// DNSOneAddrs are the DNS-01 challenge server bind addresses/ports
DNSOneAddrs []string
// TLSALPNOneAddrs are the TLS-ALPN-01 challenge server bind addresses/ports
TLSALPNOneAddrs []string
// DOHCert is required if DOHAddrs is nonempty.
DOHCert string
// DOHCertKey is required if DOHAddrs is nonempty.
DOHCertKey string
}
// validate checks that a challenge server Config is valid. To be valid it must
// specify a bind address for at least one challenge type. If there is no
// configured log in the config a default is provided.
func (c *Config) validate() error {
// There needs to be at least one challenge type with a bind address
if len(c.HTTPOneAddrs) < 1 &&
len(c.HTTPSOneAddrs) < 1 &&
len(c.DNSOneAddrs) < 1 &&
len(c.TLSALPNOneAddrs) < 1 {
return fmt.Errorf(
"config must specify at least one HTTPOneAddrs entry, one HTTPSOneAddr " +
"entry, one DOHAddrs, one DNSOneAddrs entry, or one TLSALPNOneAddrs entry")
}
// If there is no configured log make a default with a prefix
if c.Log == nil {
c.Log = log.New(os.Stdout, "challtestsrv - ", log.LstdFlags)
}
return nil
}
// New constructs and returns a new ChallSrv instance with the given Config.
func New(config Config) (*ChallSrv, error) {
// Validate the provided configuration
if err := config.validate(); err != nil {
return nil, err
}
challSrv := &ChallSrv{
log: config.Log,
requestHistory: make(map[string]map[RequestEventType][]RequestEvent),
httpOne: make(map[string]string),
dnsOne: make(map[string][]string),
tlsALPNOne: make(map[string]string),
redirects: make(map[string]string),
dnsMocks: mockDNSData{
defaultIPv4: defaultIPv4,
defaultIPv6: defaultIPv6,
aRecords: make(map[string][]string),
aaaaRecords: make(map[string][]string),
caaRecords: make(map[string][]MockCAAPolicy),
cnameRecords: make(map[string]string),
servFailRecords: make(map[string]bool),
},
}
// If there are HTTP-01 addresses configured, create HTTP-01 servers with
// HTTPS disabled.
for _, address := range config.HTTPOneAddrs {
challSrv.log.Printf("Creating HTTP-01 challenge server on %s\n", address)
challSrv.servers = append(challSrv.servers, httpOneServer(address, challSrv, false))
}
// If there are HTTPS HTTP-01 addresses configured, create HTTP-01 servers
// with HTTPS enabled.
for _, address := range config.HTTPSOneAddrs {
challSrv.log.Printf("Creating HTTPS HTTP-01 challenge server on %s\n", address)
challSrv.servers = append(challSrv.servers, httpOneServer(address, challSrv, true))
}
// If there are DNS-01 addresses configured, create DNS-01 servers
for _, address := range config.DNSOneAddrs {
challSrv.log.Printf("Creating TCP and UDP DNS-01 challenge server on %s\n", address)
challSrv.servers = append(challSrv.servers,
dnsOneServer(address, challSrv.dnsHandler)...)
}
for _, address := range config.DOHAddrs {
challSrv.log.Printf("Creating DoH server on %s\n", address)
s, err := dohServer(address, config.DOHCert, config.DOHCertKey, http.HandlerFunc(challSrv.dohHandler))
if err != nil {
return nil, err
}
challSrv.servers = append(challSrv.servers, s)
}
// If there are TLS-ALPN-01 addresses configured, create TLS-ALPN-01 servers
for _, address := range config.TLSALPNOneAddrs {
challSrv.log.Printf("Creating TLS-ALPN-01 challenge server on %s\n", address)
challSrv.servers = append(challSrv.servers, tlsALPNOneServer(address, challSrv))
}
return challSrv, nil
}
// Run starts each of the ChallSrv's challengeServers.
func (s *ChallSrv) Run() {
s.log.Printf("Starting challenge servers")
// Start each server in their own dedicated Go routine
for _, srv := range s.servers {
go func(srv challengeServer) {
err := srv.ListenAndServe()
if err != nil && !strings.Contains(err.Error(), "Server closed") {
s.log.Print(err)
}
}(srv)
}
}
// Shutdown gracefully stops each of the ChallSrv's challengeServers.
func (s *ChallSrv) Shutdown() {
for _, srv := range s.servers {
if err := srv.Shutdown(); err != nil {
s.log.Printf("err in Shutdown(): %s\n", err.Error())
}
}
}