diff options
author | Tom Barrett <tom@tombarrett.xyz> | 2023-11-01 17:57:48 +0100 |
---|---|---|
committer | Tom Barrett <tom@tombarrett.xyz> | 2023-11-01 18:11:33 +0100 |
commit | 240c3d1338415e5d82ef7ca0e52c4284be6441bd (patch) | |
tree | 4b0ee5d208c2cdffa78d65f1b0abe0ec85f15652 /modules/caddypki/acmeserver | |
parent | 73e78ab226f21e6c6c68961af88c4ab9c746f4f4 (diff) | |
parent | 0e204b730aa2b1fa0835336b1117eff8c420f713 (diff) |
vbump to v2.7.5
Diffstat (limited to 'modules/caddypki/acmeserver')
-rw-r--r-- | modules/caddypki/acmeserver/acmeserver.go | 130 | ||||
-rw-r--r-- | modules/caddypki/acmeserver/caddyfile.go | 11 |
2 files changed, 116 insertions, 25 deletions
diff --git a/modules/caddypki/acmeserver/acmeserver.go b/modules/caddypki/acmeserver/acmeserver.go index 6ecdfdc..8689615 100644 --- a/modules/caddypki/acmeserver/acmeserver.go +++ b/modules/caddypki/acmeserver/acmeserver.go @@ -15,7 +15,10 @@ package acmeserver import ( + "context" "fmt" + weakrand "math/rand" + "net" "net/http" "os" "path/filepath" @@ -23,18 +26,19 @@ import ( "strings" "time" - "github.com/caddyserver/caddy/v2" - "github.com/caddyserver/caddy/v2/modules/caddyhttp" - "github.com/caddyserver/caddy/v2/modules/caddypki" "github.com/go-chi/chi" "github.com/smallstep/certificates/acme" - acmeAPI "github.com/smallstep/certificates/acme/api" + "github.com/smallstep/certificates/acme/api" acmeNoSQL "github.com/smallstep/certificates/acme/db/nosql" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/db" "github.com/smallstep/nosql" "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddypki" ) func init() { @@ -76,8 +80,26 @@ type Handler struct { // changed or removed in the future. SignWithRoot bool `json:"sign_with_root,omitempty"` + // The addresses of DNS resolvers to use when looking up + // the TXT records for solving DNS challenges. + // It accepts [network addresses](/docs/conventions#network-addresses) + // with port range of only 1. If the host is an IP address, + // it will be dialed directly to resolve the upstream server. + // If the host is not an IP address, the addresses are resolved + // using the [name resolution convention](https://golang.org/pkg/net/#hdr-Name_Resolution) + // of the Go standard library. If the array contains more + // than 1 resolver address, one is chosen at random. + Resolvers []string `json:"resolvers,omitempty"` + + logger *zap.Logger + resolvers []caddy.NetworkAddress + ctx caddy.Context + + acmeDB acme.DB + acmeAuth *authority.Authority + acmeClient acme.Client + acmeLinker acme.Linker acmeEndpoints http.Handler - logger *zap.Logger } // CaddyModule returns the Caddy module information. @@ -90,7 +112,9 @@ func (Handler) CaddyModule() caddy.ModuleInfo { // Provision sets up the ACME server handler. func (ash *Handler) Provision(ctx caddy.Context) error { + ash.ctx = ctx ash.logger = ctx.Logger() + // set some defaults if ash.CA == "" { ash.CA = caddypki.DefaultCAID @@ -142,31 +166,30 @@ func (ash *Handler) Provision(ctx caddy.Context) error { DB: database, } - auth, err := ca.NewAuthority(authorityConfig) + ash.acmeAuth, err = ca.NewAuthority(authorityConfig) if err != nil { return err } - var acmeDB acme.DB - if authorityConfig.DB != nil { - acmeDB, err = acmeNoSQL.New(auth.GetDatabase().(nosql.DB)) - if err != nil { - return fmt.Errorf("configuring ACME DB: %v", err) - } + ash.acmeDB, err = acmeNoSQL.New(ash.acmeAuth.GetDatabase().(nosql.DB)) + if err != nil { + return fmt.Errorf("configuring ACME DB: %v", err) } - // create the router for the ACME endpoints - acmeRouterHandler := acmeAPI.NewHandler(acmeAPI.HandlerOptions{ - CA: auth, - DB: acmeDB, // stores all the server state - DNS: ash.Host, // used for directory links - Prefix: strings.Trim(ash.PathPrefix, "/"), // used for directory links - }) + ash.acmeClient, err = ash.makeClient() + if err != nil { + return err + } + + ash.acmeLinker = acme.NewLinker( + ash.Host, + strings.Trim(ash.PathPrefix, "/"), + ) // extract its http.Handler so we can use it directly r := chi.NewRouter() r.Route(ash.PathPrefix, func(r chi.Router) { - acmeRouterHandler.Route(r) + api.Route(r) }) ash.acmeEndpoints = r @@ -175,6 +198,16 @@ func (ash *Handler) Provision(ctx caddy.Context) error { func (ash Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { if strings.HasPrefix(r.URL.Path, ash.PathPrefix) { + acmeCtx := acme.NewContext( + r.Context(), + ash.acmeDB, + ash.acmeClient, + ash.acmeLinker, + nil, + ) + acmeCtx = authority.NewContext(acmeCtx, ash.acmeAuth) + r = r.WithContext(acmeCtx) + ash.acmeEndpoints.ServeHTTP(w, r) return nil } @@ -207,7 +240,7 @@ func (ash Handler) openDatabase() (*db.AuthDB, error) { dbFolder := filepath.Join(caddy.AppDataDir(), "acme_server", key) dbPath := filepath.Join(dbFolder, "db") - err := os.MkdirAll(dbFolder, 0755) + err := os.MkdirAll(dbFolder, 0o755) if err != nil { return nil, fmt.Errorf("making folder for CA database: %v", err) } @@ -227,10 +260,61 @@ func (ash Handler) openDatabase() (*db.AuthDB, error) { return database.(databaseCloser).DB, err } +// makeClient creates an ACME client which will use a custom +// resolver instead of net.DefaultResolver. +func (ash Handler) makeClient() (acme.Client, error) { + for _, v := range ash.Resolvers { + addr, err := caddy.ParseNetworkAddressWithDefaults(v, "udp", 53) + if err != nil { + return nil, err + } + if addr.PortRangeSize() != 1 { + return nil, fmt.Errorf("resolver address must have exactly one address; cannot call %v", addr) + } + ash.resolvers = append(ash.resolvers, addr) + } + + var resolver *net.Resolver + if len(ash.resolvers) != 0 { + dialer := &net.Dialer{ + Timeout: 2 * time.Second, + } + resolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + //nolint:gosec + addr := ash.resolvers[weakrand.Intn(len(ash.resolvers))] + return dialer.DialContext(ctx, addr.Network, addr.JoinHostPort(0)) + }, + } + } else { + resolver = net.DefaultResolver + } + + return resolverClient{ + Client: acme.NewClient(), + resolver: resolver, + ctx: ash.ctx, + }, nil +} + +type resolverClient struct { + acme.Client + + resolver *net.Resolver + ctx context.Context +} + +func (c resolverClient) LookupTxt(name string) ([]string, error) { + return c.resolver.LookupTXT(c.ctx, name) +} + const defaultPathPrefix = "/acme/" -var keyCleaner = regexp.MustCompile(`[^\w.-_]`) -var databasePool = caddy.NewUsagePool() +var ( + keyCleaner = regexp.MustCompile(`[^\w.-_]`) + databasePool = caddy.NewUsagePool() +) type databaseCloser struct { DB *db.AuthDB diff --git a/modules/caddypki/acmeserver/caddyfile.go b/modules/caddypki/acmeserver/caddyfile.go index ae2d8ef..3b52113 100644 --- a/modules/caddypki/acmeserver/caddyfile.go +++ b/modules/caddypki/acmeserver/caddyfile.go @@ -29,8 +29,9 @@ func init() { // parseACMEServer sets up an ACME server handler from Caddyfile tokens. // // acme_server [<matcher>] { -// ca <id> -// lifetime <duration> +// ca <id> +// lifetime <duration> +// resolvers <addresses...> // } func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { if !h.Next() { @@ -74,6 +75,12 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error } acmeServer.Lifetime = caddy.Duration(dur) + + case "resolvers": + acmeServer.Resolvers = h.RemainingArgs() + if len(acmeServer.Resolvers) == 0 { + return nil, h.Errf("must specify at least one resolver address") + } } } } |