summaryrefslogtreecommitdiff
path: root/modules/caddypki
diff options
context:
space:
mode:
Diffstat (limited to 'modules/caddypki')
-rw-r--r--modules/caddypki/acmeserver/acmeserver.go130
-rw-r--r--modules/caddypki/acmeserver/caddyfile.go11
-rw-r--r--modules/caddypki/adminapi.go19
-rw-r--r--modules/caddypki/ca.go7
-rw-r--r--modules/caddypki/command.go42
-rw-r--r--modules/caddypki/pki.go3
6 files changed, 149 insertions, 63 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")
+ }
}
}
}
diff --git a/modules/caddypki/adminapi.go b/modules/caddypki/adminapi.go
index f03c6b6..435af34 100644
--- a/modules/caddypki/adminapi.go
+++ b/modules/caddypki/adminapi.go
@@ -20,8 +20,9 @@ import (
"net/http"
"strings"
- "github.com/caddyserver/caddy/v2"
"go.uber.org/zap"
+
+ "github.com/caddyserver/caddy/v2"
)
func init() {
@@ -49,20 +50,10 @@ func (a *adminAPI) Provision(ctx caddy.Context) error {
a.ctx = ctx
a.log = ctx.Logger(a) // TODO: passing in 'a' is a hack until the admin API is officially extensible (see #5032)
- // First check if the PKI app was configured, because
- // a.ctx.App() has the side effect of instantiating
- // and provisioning an app even if it wasn't configured.
- pkiAppConfigured := a.ctx.AppIsConfigured("pki")
- if !pkiAppConfigured {
- return nil
- }
-
- // Load the PKI app, so we can query it for information.
- appModule, err := a.ctx.App("pki")
- if err != nil {
- return err
+ // Avoid initializing PKI if it wasn't configured
+ if pkiApp := a.ctx.AppIfConfigured("pki"); pkiApp != nil {
+ a.pkiApp = pkiApp.(*PKI)
}
- a.pkiApp = appModule.(*PKI)
return nil
}
diff --git a/modules/caddypki/ca.go b/modules/caddypki/ca.go
index 1ba0890..6c48da6 100644
--- a/modules/caddypki/ca.go
+++ b/modules/caddypki/ca.go
@@ -25,12 +25,13 @@ import (
"sync"
"time"
- "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/certmagic"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/db"
"github.com/smallstep/truststore"
"go.uber.org/zap"
+
+ "github.com/caddyserver/caddy/v2"
)
// CA describes a certificate authority, which consists of
@@ -376,15 +377,19 @@ func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer)
func (ca CA) storageKeyCAPrefix() string {
return path.Join("pki", "authorities", certmagic.StorageKeys.Safe(ca.ID))
}
+
func (ca CA) storageKeyRootCert() string {
return path.Join(ca.storageKeyCAPrefix(), "root.crt")
}
+
func (ca CA) storageKeyRootKey() string {
return path.Join(ca.storageKeyCAPrefix(), "root.key")
}
+
func (ca CA) storageKeyIntermediateCert() string {
return path.Join(ca.storageKeyCAPrefix(), "intermediate.crt")
}
+
func (ca CA) storageKeyIntermediateKey() string {
return path.Join(ca.storageKeyCAPrefix(), "intermediate.key")
}
diff --git a/modules/caddypki/command.go b/modules/caddypki/command.go
index cb86c93..b7fa1bb 100644
--- a/modules/caddypki/command.go
+++ b/modules/caddypki/command.go
@@ -18,21 +18,22 @@ import (
"crypto/x509"
"encoding/json"
"encoding/pem"
- "flag"
"fmt"
"net/http"
"os"
"path"
- "github.com/caddyserver/caddy/v2"
- caddycmd "github.com/caddyserver/caddy/v2/cmd"
"github.com/smallstep/truststore"
+ "github.com/spf13/cobra"
+
+ caddycmd "github.com/caddyserver/caddy/v2/cmd"
+
+ "github.com/caddyserver/caddy/v2"
)
func init() {
caddycmd.RegisterCommand(caddycmd.Command{
Name: "trust",
- Func: cmdTrust,
Usage: "[--ca <id>] [--address <listen>] [--config <path> [--adapter <name>]]",
Short: "Installs a CA certificate into local trust stores",
Long: `
@@ -53,19 +54,17 @@ This command will attempt to connect to Caddy's admin API running at
'` + caddy.DefaultAdminListen + `' to fetch the root certificate. You may
explicitly specify the --address, or use the --config flag to load
the admin address from your config, if not using the default.`,
- Flags: func() *flag.FlagSet {
- fs := flag.NewFlagSet("trust", flag.ExitOnError)
- fs.String("ca", "", "The ID of the CA to trust (defaults to 'local')")
- fs.String("address", "", "Address of the administration API listener (if --config is not used)")
- fs.String("config", "", "Configuration file (if --address is not used)")
- fs.String("adapter", "", "Name of config adapter to apply (if --config is used)")
- return fs
- }(),
+ CobraFunc: func(cmd *cobra.Command) {
+ cmd.Flags().StringP("ca", "", "", "The ID of the CA to trust (defaults to 'local')")
+ cmd.Flags().StringP("address", "", "", "Address of the administration API listener (if --config is not used)")
+ cmd.Flags().StringP("config", "c", "", "Configuration file (if --address is not used)")
+ cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (if --config is used)")
+ cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdTrust)
+ },
})
caddycmd.RegisterCommand(caddycmd.Command{
Name: "untrust",
- Func: cmdUntrust,
Usage: "[--cert <path>] | [[--ca <id>] [--address <listen>] [--config <path> [--adapter <name>]]]",
Short: "Untrusts a locally-trusted CA certificate",
Long: `
@@ -89,15 +88,14 @@ will attempt to connect to the Caddy's admin API running at
'` + caddy.DefaultAdminListen + `' to fetch the root certificate.
You may explicitly specify the --address, or use the --config flag
to load the admin address from your config, if not using the default.`,
- Flags: func() *flag.FlagSet {
- fs := flag.NewFlagSet("untrust", flag.ExitOnError)
- fs.String("cert", "", "The path to the CA certificate to untrust")
- fs.String("ca", "", "The ID of the CA to untrust (defaults to 'local')")
- fs.String("address", "", "Address of the administration API listener (if --config is not used)")
- fs.String("config", "", "Configuration file (if --address is not used)")
- fs.String("adapter", "", "Name of config adapter to apply (if --config is used)")
- return fs
- }(),
+ CobraFunc: func(cmd *cobra.Command) {
+ cmd.Flags().StringP("cert", "p", "", "The path to the CA certificate to untrust")
+ cmd.Flags().StringP("ca", "", "", "The ID of the CA to untrust (defaults to 'local')")
+ cmd.Flags().StringP("address", "", "", "Address of the administration API listener (if --config is not used)")
+ cmd.Flags().StringP("config", "c", "", "Configuration file (if --address is not used)")
+ cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (if --config is used)")
+ cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdUntrust)
+ },
})
}
diff --git a/modules/caddypki/pki.go b/modules/caddypki/pki.go
index 2caeb2b..9f974a9 100644
--- a/modules/caddypki/pki.go
+++ b/modules/caddypki/pki.go
@@ -17,8 +17,9 @@ package caddypki
import (
"fmt"
- "github.com/caddyserver/caddy/v2"
"go.uber.org/zap"
+
+ "github.com/caddyserver/caddy/v2"
)
func init() {