Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 13, 03:00 UTC
acmeserver: Configurable `resolvers`, fix smallstep deprecations (#5500)
* acmeserver: Configurable `resolvers`, fix smallstep deprecations * Improve default net/port * Update proxy resolvers parsing to use the new function * Update listeners.go Co-authored-by: itsxaos <33079230+itsxaos@users.noreply.github.com> --------- Co-authored-by: itsxaos <33079230+itsxaos@users.noreply.github.com>
Francis Lavoie committed
May 3, 2023 at 13:07 UTC
3f20a7c9f348122d5fae7074b9fa17651189bb9a
5 files changed
+283
-52
listeners.go
+11
-2
@@ -303,13 +303,19 @@ func IsUnixNetwork(netw string) bool {
303
// Network addresses are distinct from URLs and do not
304
// use URL syntax.
305
func ParseNetworkAddress(addr string) (NetworkAddress, error) {
306
+ return ParseNetworkAddressWithDefaults(addr, "tcp", 0)
307
+}
308
+
309
+// ParseNetworkAddressWithDefaults is like ParseNetworkAddress but allows
310
+// the default network and port to be specified.
311
+func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort uint) (NetworkAddress, error) {
312
var host, port string
313
network, host, port, err := SplitNetworkAddress(addr)
314
if err != nil {
315
return NetworkAddress{}, err
316
}
317
if network == "" {
312
- network = "tcp"
318
+ network = defaultNetwork
319
}
320
if IsUnixNetwork(network) {
321
return NetworkAddress{
@@ -318,7 +324,10 @@ func ParseNetworkAddress(addr string) (NetworkAddress, error) {
324
}, nil
325
}
326
var start, end uint64
321
- if port != "" {
327
+ if port == "" {
328
+ start = uint64(defaultPort)
329
+ end = uint64(defaultPort)
330
+ } else {
331
before, after, found := strings.Cut(port, "-")
332
if !found {
333
after = before
listeners_test.go
+163
-21
@@ -175,47 +175,57 @@ func TestJoinNetworkAddress(t *testing.T) {
175
176
func TestParseNetworkAddress(t *testing.T) {
177
for i, tc := range []struct {
178
- input string
179
- expectAddr NetworkAddress
180
- expectErr bool
178
+ input string
179
+ defaultNetwork string
180
+ defaultPort uint
181
+ expectAddr NetworkAddress
182
+ expectErr bool
183
}{
184
{
185
input: "",
186
expectErr: true,
187
},
188
{
187
- input: ":",
189
+ input: ":",
190
+ defaultNetwork: "udp",
191
expectAddr: NetworkAddress{
189
- Network: "tcp",
192
+ Network: "udp",
193
},
194
},
195
{
193
- input: "[::]",
196
+ input: "[::]",
197
+ defaultNetwork: "udp",
198
+ defaultPort: 53,
199
expectAddr: NetworkAddress{
195
- Network: "tcp",
196
- Host: "::",
200
+ Network: "udp",
201
+ Host: "::",
202
+ StartPort: 53,
203
+ EndPort: 53,
204
},
205
},
206
{
200
- input: ":1234",
207
+ input: ":1234",
208
+ defaultNetwork: "udp",
209
expectAddr: NetworkAddress{
202
- Network: "tcp",
210
+ Network: "udp",
211
Host: "",
212
StartPort: 1234,
213
EndPort: 1234,
214
},
215
},
216
{
209
- input: "tcp/:1234",
217
+ input: "udp/:1234",
218
+ defaultNetwork: "udp",
219
expectAddr: NetworkAddress{
211
- Network: "tcp",
220
+ Network: "udp",
221
Host: "",
222
StartPort: 1234,
223
EndPort: 1234,
224
},
225
},
226
{
218
- input: "tcp6/:1234",
227
+ input: "tcp6/:1234",
228
+ defaultNetwork: "tcp",
229
expectAddr: NetworkAddress{
230
Network: "tcp6",
231
Host: "",
@@ -224,7 +234,8 @@ func TestParseNetworkAddress(t *testing.T) {
234
},
235
},
236
{
227
- input: "tcp4/localhost:1234",
237
+ input: "tcp4/localhost:1234",
238
+ defaultNetwork: "tcp",
239
expectAddr: NetworkAddress{
240
Network: "tcp4",
241
Host: "localhost",
@@ -233,14 +244,16 @@ func TestParseNetworkAddress(t *testing.T) {
244
},
245
},
246
{
236
- input: "unix//foo/bar",
247
+ input: "unix//foo/bar",
248
+ defaultNetwork: "tcp",
249
expectAddr: NetworkAddress{
250
Network: "unix",
251
Host: "/foo/bar",
252
},
253
},
254
{
243
- input: "localhost:1234-1234",
255
+ input: "localhost:1234-1234",
256
+ defaultNetwork: "tcp",
257
expectAddr: NetworkAddress{
258
Network: "tcp",
259
Host: "localhost",
@@ -249,11 +262,13 @@ func TestParseNetworkAddress(t *testing.T) {
262
},
263
},
264
{
252
- input: "localhost:2-1",
253
- expectErr: true,
265
+ input: "localhost:2-1",
266
+ defaultNetwork: "tcp",
267
+ expectErr: true,
268
},
269
{
256
- input: "localhost:0",
270
+ input: "localhost:0",
271
+ defaultNetwork: "tcp",
272
expectAddr: NetworkAddress{
273
Network: "tcp",
274
Host: "localhost",
@@ -262,11 +277,138 @@ func TestParseNetworkAddress(t *testing.T) {
277
},
278
},
279
{
265
- input: "localhost:1-999999999999",
280
+ input: "localhost:1-999999999999",
281
+ defaultNetwork: "tcp",
282
+ expectErr: true,
283
+ },
284
+ } {
285
+ actualAddr, err := ParseNetworkAddressWithDefaults(tc.input, tc.defaultNetwork, tc.defaultPort)
286
+ if tc.expectErr && err == nil {
287
+ t.Errorf("Test %d: Expected error but got: %v", i, err)
288
+ }
289
+ if !tc.expectErr && err != nil {
290
+ t.Errorf("Test %d: Expected no error but got: %v", i, err)
291
+ }
292
+
293
+ if actualAddr.Network != tc.expectAddr.Network {
294
+ t.Errorf("Test %d: Expected network '%v' but got '%v'", i, tc.expectAddr, actualAddr)
295
+ }
296
+ if !reflect.DeepEqual(tc.expectAddr, actualAddr) {
297
+ t.Errorf("Test %d: Expected addresses %v but got %v", i, tc.expectAddr, actualAddr)
298
+ }
299
+ }
300
+}
301
+
302
+func TestParseNetworkAddressWithDefaults(t *testing.T) {
303
+ for i, tc := range []struct {
304
+ input string
305
+ defaultNetwork string
306
+ defaultPort uint
307
+ expectAddr NetworkAddress
308
+ expectErr bool
309
+ }{
310
+ {
311
+ input: "",
312
expectErr: true,
313
},
314
+ {
315
+ input: ":",
316
+ defaultNetwork: "udp",
317
+ expectAddr: NetworkAddress{
318
+ Network: "udp",
319
+ },
320
+ },
321
+ {
322
+ input: "[::]",
323
+ defaultNetwork: "udp",
324
+ defaultPort: 53,
325
+ expectAddr: NetworkAddress{
326
+ Network: "udp",
327
+ Host: "::",
328
+ StartPort: 53,
329
+ EndPort: 53,
330
+ },
331
+ },
332
+ {
333
+ input: ":1234",
334
+ defaultNetwork: "udp",
335
+ expectAddr: NetworkAddress{
336
+ Network: "udp",
337
+ Host: "",
338
+ StartPort: 1234,
339
+ EndPort: 1234,
340
+ },
341
+ },
342
+ {
343
+ input: "udp/:1234",
344
+ defaultNetwork: "udp",
345
+ expectAddr: NetworkAddress{
346
+ Network: "udp",
347
+ Host: "",
348
+ StartPort: 1234,
349
+ EndPort: 1234,
350
+ },
351
+ },
352
+ {
353
+ input: "tcp6/:1234",
354
+ defaultNetwork: "tcp",
355
+ expectAddr: NetworkAddress{
356
+ Network: "tcp6",
357
+ Host: "",
358
+ StartPort: 1234,
359
+ EndPort: 1234,
360
+ },
361
+ },
362
+ {
363
+ input: "tcp4/localhost:1234",
364
+ defaultNetwork: "tcp",
365
+ expectAddr: NetworkAddress{
366
+ Network: "tcp4",
367
+ Host: "localhost",
368
+ StartPort: 1234,
369
+ EndPort: 1234,
370
+ },
371
+ },
372
+ {
373
+ input: "unix//foo/bar",
374
+ defaultNetwork: "tcp",
375
+ expectAddr: NetworkAddress{
376
+ Network: "unix",
377
+ Host: "/foo/bar",
378
+ },
379
+ },
380
+ {
381
+ input: "localhost:1234-1234",
382
+ defaultNetwork: "tcp",
383
+ expectAddr: NetworkAddress{
384
+ Network: "tcp",
385
+ Host: "localhost",
386
+ StartPort: 1234,
387
+ EndPort: 1234,
388
+ },
389
+ },
390
+ {
391
+ input: "localhost:2-1",
392
+ defaultNetwork: "tcp",
393
+ expectErr: true,
394
+ },
395
+ {
396
+ input: "localhost:0",
397
+ defaultNetwork: "tcp",
398
+ expectAddr: NetworkAddress{
399
+ Network: "tcp",
400
+ Host: "localhost",
401
+ StartPort: 0,
402
+ EndPort: 0,
403
+ },
404
+ },
405
+ {
406
+ input: "localhost:1-999999999999",
407
+ defaultNetwork: "tcp",
408
+ expectErr: true,
409
+ },
410
} {
269
- actualAddr, err := ParseNetworkAddress(tc.input)
411
+ actualAddr, err := ParseNetworkAddressWithDefaults(tc.input, tc.defaultNetwork, tc.defaultPort)
412
if tc.expectErr && err == nil {
413
t.Errorf("Test %d: Expected error but got: %v", i, err)
414
}
modules/caddyhttp/reverseproxy/upstreams.go
+2
-10
@@ -8,7 +8,6 @@ import (
8
"net"
9
"net/http"
10
"strconv"
11
- "strings"
11
"sync"
12
"time"
13
@@ -471,16 +470,9 @@ type UpstreamResolver struct {
470
// and ensures they're ready to be used.
471
func (u *UpstreamResolver) ParseAddresses() error {
472
for _, v := range u.Addresses {
474
- addr, err := caddy.ParseNetworkAddress(v)
473
+ addr, err := caddy.ParseNetworkAddressWithDefaults(v, "udp", 53)
474
if err != nil {
476
- // If a port wasn't specified for the resolver,
477
- // try defaulting to 53 and parse again
478
- if strings.Contains(err.Error(), "missing port in address") {
479
- addr, err = caddy.ParseNetworkAddress(v + ":53")
480
- }
481
- if err != nil {
482
- return err
483
- }
475
+ return err
476
}
477
if addr.PortRangeSize() != 1 {
478
return fmt.Errorf("resolver address must have exactly one address; cannot call %v", addr)
modules/caddypki/acmeserver/acmeserver.go
+98
-17
@@ -15,7 +15,10 @@
15
package acmeserver
16
17
import (
18
+ "context"
19
"fmt"
20
+ weakrand "math/rand"
21
+ "net"
22
"net/http"
23
"os"
24
"path/filepath"
@@ -28,7 +31,7 @@ import (
31
"github.com/caddyserver/caddy/v2/modules/caddypki"
32
"github.com/go-chi/chi"
33
"github.com/smallstep/certificates/acme"
31
- acmeAPI "github.com/smallstep/certificates/acme/api"
34
+ "github.com/smallstep/certificates/acme/api"
35
acmeNoSQL "github.com/smallstep/certificates/acme/db/nosql"
36
"github.com/smallstep/certificates/authority"
37
"github.com/smallstep/certificates/authority/provisioner"
@@ -76,8 +79,26 @@ type Handler struct {
79
// changed or removed in the future.
80
SignWithRoot bool `json:"sign_with_root,omitempty"`
81
82
+ // The addresses of DNS resolvers to use when looking up
83
+ // the TXT records for solving DNS challenges.
84
+ // It accepts [network addresses](/docs/conventions#network-addresses)
85
+ // with port range of only 1. If the host is an IP address,
86
+ // it will be dialed directly to resolve the upstream server.
87
+ // If the host is not an IP address, the addresses are resolved
88
+ // using the [name resolution convention](https://golang.org/pkg/net/#hdr-Name_Resolution)
89
+ // of the Go standard library. If the array contains more
90
+ // than 1 resolver address, one is chosen at random.
91
+ Resolvers []string `json:"resolvers,omitempty"`
92
+
93
+ logger *zap.Logger
94
+ resolvers []caddy.NetworkAddress
95
+ ctx caddy.Context
96
+
97
+ acmeDB acme.DB
98
+ acmeAuth *authority.Authority
99
+ acmeClient acme.Client
100
+ acmeLinker acme.Linker
101
acmeEndpoints http.Handler
80
- logger *zap.Logger
102
}
103
104
// CaddyModule returns the Caddy module information.
@@ -90,7 +111,9 @@ func (Handler) CaddyModule() caddy.ModuleInfo {
111
112
// Provision sets up the ACME server handler.
113
func (ash *Handler) Provision(ctx caddy.Context) error {
114
+ ash.ctx = ctx
115
ash.logger = ctx.Logger()
116
+
117
// set some defaults
118
if ash.CA == "" {
119
ash.CA = caddypki.DefaultCAID
@@ -142,31 +165,30 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
165
DB: database,
166
}
167
145
- auth, err := ca.NewAuthority(authorityConfig)
168
+ ash.acmeAuth, err = ca.NewAuthority(authorityConfig)
169
if err != nil {
170
return err
171
}
172
150
- var acmeDB acme.DB
151
- if authorityConfig.DB != nil {
152
- acmeDB, err = acmeNoSQL.New(auth.GetDatabase().(nosql.DB))
153
- if err != nil {
154
- return fmt.Errorf("configuring ACME DB: %v", err)
155
- }
173
+ ash.acmeDB, err = acmeNoSQL.New(ash.acmeAuth.GetDatabase().(nosql.DB))
174
+ if err != nil {
175
+ return fmt.Errorf("configuring ACME DB: %v", err)
176
}
177
158
- // create the router for the ACME endpoints
159
- acmeRouterHandler := acmeAPI.NewHandler(acmeAPI.HandlerOptions{
160
- CA: auth,
161
- DB: acmeDB, // stores all the server state
162
- DNS: ash.Host, // used for directory links
163
- Prefix: strings.Trim(ash.PathPrefix, "/"), // used for directory links
164
- })
178
+ ash.acmeClient, err = ash.makeClient()
179
+ if err != nil {
180
+ return err
181
+ }
182
+
183
+ ash.acmeLinker = acme.NewLinker(
184
+ ash.Host,
185
+ strings.Trim(ash.PathPrefix, "/"),
186
+ )
187
188
// extract its http.Handler so we can use it directly
189
r := chi.NewRouter()
190
r.Route(ash.PathPrefix, func(r chi.Router) {
169
- acmeRouterHandler.Route(r)
191
+ api.Route(r)
192
})
193
ash.acmeEndpoints = r
194
@@ -175,6 +197,16 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
197
198
func (ash Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
199
if strings.HasPrefix(r.URL.Path, ash.PathPrefix) {
200
+ acmeCtx := acme.NewContext(
201
+ r.Context(),
202
+ ash.acmeDB,
203
+ ash.acmeClient,
204
+ ash.acmeLinker,
205
+ nil,
206
+ )
207
+ acmeCtx = authority.NewContext(acmeCtx, ash.acmeAuth)
208
+ r = r.WithContext(acmeCtx)
209
+
210
ash.acmeEndpoints.ServeHTTP(w, r)
211
return nil
212
}
@@ -227,6 +259,55 @@ func (ash Handler) openDatabase() (*db.AuthDB, error) {
259
return database.(databaseCloser).DB, err
260
}
261
262
+// makeClient creates an ACME client which will use a custom
263
+// resolver instead of net.DefaultResolver.
264
+func (ash Handler) makeClient() (acme.Client, error) {
265
+ for _, v := range ash.Resolvers {
266
+ addr, err := caddy.ParseNetworkAddressWithDefaults(v, "udp", 53)
267
+ if err != nil {
268
+ return nil, err
269
+ }
270
+ if addr.PortRangeSize() != 1 {
271
+ return nil, fmt.Errorf("resolver address must have exactly one address; cannot call %v", addr)
272
+ }
273
+ ash.resolvers = append(ash.resolvers, addr)
274
+ }
275
+
276
+ var resolver *net.Resolver
277
+ if len(ash.resolvers) != 0 {
278
+ dialer := &net.Dialer{
279
+ Timeout: 2 * time.Second,
280
+ }
281
+ resolver = &net.Resolver{
282
+ PreferGo: true,
283
+ Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
284
+ //nolint:gosec
285
+ addr := ash.resolvers[weakrand.Intn(len(ash.resolvers))]
286
+ return dialer.DialContext(ctx, addr.Network, addr.JoinHostPort(0))
287
+ },
288
+ }
289
+ } else {
290
+ resolver = net.DefaultResolver
291
+ }
292
+
293
+ return resolverClient{
294
+ Client: acme.NewClient(),
295
+ resolver: resolver,
296
+ ctx: ash.ctx,
297
+ }, nil
298
+}
299
+
300
+type resolverClient struct {
301
+ acme.Client
302
+
303
+ resolver *net.Resolver
304
+ ctx context.Context
305
+}
306
+
307
+func (c resolverClient) LookupTxt(name string) ([]string, error) {
308
+ return c.resolver.LookupTXT(c.ctx, name)
309
+}
310
+
311
const defaultPathPrefix = "/acme/"
312
313
var keyCleaner = regexp.MustCompile(`[^\w.-_]`)
modules/caddypki/acmeserver/caddyfile.go
+9
-2
@@ -29,8 +29,9 @@ func init() {
29
// parseACMEServer sets up an ACME server handler from Caddyfile tokens.
30
//
31
// acme_server [<matcher>] {
32
-// ca <id>
33
-// lifetime <duration>
32
+// ca <id>
33
+// lifetime <duration>
34
+// resolvers <addresses...>
35
// }
36
func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
37
if !h.Next() {
@@ -74,6 +75,12 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
75
}
76
77
acmeServer.Lifetime = caddy.Duration(dur)
78
+
79
+ case "resolvers":
80
+ acmeServer.Resolvers = h.RemainingArgs()
81
+ if len(acmeServer.Resolvers) == 0 {
82
+ return nil, h.Errf("must specify at least one resolver address")
83
+ }
84
}
85
}
86
}