@marcel91200 / caddy Mirror · read-only / commits / 3f20a7c9
Fork 0

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 }