summaryrefslogtreecommitdiff
path: root/modules/caddypki/command.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/caddypki/command.go')
-rw-r--r--modules/caddypki/command.go201
1 files changed, 152 insertions, 49 deletions
diff --git a/modules/caddypki/command.go b/modules/caddypki/command.go
index 34daefa..fa37ab0 100644
--- a/modules/caddypki/command.go
+++ b/modules/caddypki/command.go
@@ -15,11 +15,13 @@
package caddypki
import (
- "context"
+ "crypto/x509"
+ "encoding/json"
+ "encoding/pem"
"flag"
"fmt"
+ "net/http"
"os"
- "path/filepath"
"github.com/caddyserver/caddy/v2"
caddycmd "github.com/caddyserver/caddy/v2/cmd"
@@ -30,69 +32,110 @@ 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: `
-Adds a root certificate into the local trust stores. Intended for
-development environments only.
-
-Since Caddy will install its root certificates into the local trust
-stores automatically when they are first generated, this command is
-only necessary if you need to pre-install the certificates before
-using them; for example, if you have elevated privileges at one
-point but not later, you will want to use this command so that a
-password prompt is not required later.
-
-This command installs the root certificate only for Caddy's
-default CA.`,
+Adds a root certificate into the local trust stores.
+
+Caddy will attempt to install its root certificates into the local
+trust stores automatically when they are first generated, but it
+might fail if Caddy doesn't have the appropriate permissions to
+write to the trust store. This command is necessary to pre-install
+the certificates before using them, if the server process runs as an
+unprivileged user (such as via systemd).
+
+By default, this command installs the root certificate for Caddy's
+default CA (i.e. 'local'). You may specify the ID of another CA
+with the --ca flag.
+
+Also, this command 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("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
+ }(),
})
caddycmd.RegisterCommand(caddycmd.Command{
Name: "untrust",
Func: cmdUntrust,
- Usage: "[--ca <id> | --cert <path>]",
+ Usage: "[--cert <path>] | [[--ca <id>] [--address <listen>] [--config <path> [--adapter <name>]]]",
Short: "Untrusts a locally-trusted CA certificate",
Long: `
-Untrusts a root certificate from the local trust store(s). Intended
-for development environments only.
+Untrusts a root certificate from the local trust store(s).
This command uninstalls trust; it does not necessarily delete the
root certificate from trust stores entirely. Thus, repeatedly
trusting and untrusting new certificates can fill up trust databases.
-This command does not delete or modify certificate files.
+This command does not delete or modify certificate files from Caddy's
+configured storage.
-Specify which certificate to untrust either by the ID of its CA with
-the --ca flag, or the direct path to the certificate file with the
---cert flag. If the --ca flag is used, only the default storage paths
-are assumed (i.e. using --ca flag with custom storage backends or file
-paths will not work).
+This command can be used in one of two ways. Either by specifying
+which certificate to untrust by a direct path to the certificate
+file with the --cert flag, or by fetching the root certificate for
+the CA from the admin API (default behaviour).
-If no flags are specified, --ca=local is assumed.`,
+If the admin API is used, then the CA defaults to 'local'. You may
+specify the ID of another CA with the --ca flag. By default, this
+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("ca", "", "The ID of the CA to untrust")
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
}(),
})
}
-func cmdTrust(fs caddycmd.Flags) (int, error) {
- // we have to create a sort of dummy context so that
- // the CA can provision itself...
- ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
- defer cancel()
+func cmdTrust(fl caddycmd.Flags) (int, error) {
+ caID := fl.String("ca")
+ addrFlag := fl.String("address")
+ configFlag := fl.String("config")
+ configAdapterFlag := fl.String("adapter")
- // provision the CA, which generates and stores a root
- // certificate if one doesn't already exist in storage
- ca := CA{
- storage: caddy.DefaultStorage,
+ // Prepare the URI to the admin endpoint
+ if caID == "" {
+ caID = DefaultCAID
}
- err := ca.Provision(ctx, DefaultCAID, caddy.Log())
+
+ // Determine where we're sending the request to get the CA info
+ adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, configFlag, configAdapterFlag)
+ if err != nil {
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
+ }
+
+ // Fetch the root cert from the admin API
+ rootCert, err := rootCertFromAdmin(adminAddr, caID)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
+ // Set up the CA struct; we only need to fill in the root
+ // because we're only using it to make use of the installRoot()
+ // function. Also needs a logger for warnings, and a "cert path"
+ // for the root cert; since we're loading from the API and we
+ // don't know the actual storage path via this flow, we'll just
+ // pass through the admin API address instead.
+ ca := CA{
+ log: caddy.Log(),
+ root: rootCert,
+ rootCertPath: adminAddr + adminPKICertificatesEndpoint + caID,
+ }
+
+ // Install the cert!
err = ca.installRoot()
if err != nil {
return caddy.ExitCodeFailedStartup, err
@@ -101,33 +144,93 @@ func cmdTrust(fs caddycmd.Flags) (int, error) {
return caddy.ExitCodeSuccess, nil
}
-func cmdUntrust(fs caddycmd.Flags) (int, error) {
- ca := fs.String("ca")
- cert := fs.String("cert")
+func cmdUntrust(fl caddycmd.Flags) (int, error) {
+ certFile := fl.String("cert")
+ caID := fl.String("ca")
+ addrFlag := fl.String("address")
+ configFlag := fl.String("config")
+ configAdapterFlag := fl.String("adapter")
- if ca != "" && cert != "" {
- return caddy.ExitCodeFailedStartup, fmt.Errorf("conflicting command line arguments")
+ if certFile != "" && (caID != "" || addrFlag != "" || configFlag != "") {
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("conflicting command line arguments, cannot use --cert with other flags")
}
- if ca == "" && cert == "" {
- ca = DefaultCAID
+
+ // If a file was specified, try to uninstall the cert matching that file
+ if certFile != "" {
+ // Sanity check, make sure cert file exists first
+ _, err := os.Stat(certFile)
+ if err != nil {
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("accessing certificate file: %v", err)
+ }
+
+ // Uninstall the file!
+ err = truststore.UninstallFile(certFile,
+ truststore.WithDebug(),
+ truststore.WithFirefox(),
+ truststore.WithJava())
+ if err != nil {
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to uninstall certificate file: %v", err)
+ }
+
+ return caddy.ExitCodeSuccess, nil
}
- if ca != "" {
- cert = filepath.Join(caddy.AppDataDir(), "pki", "authorities", ca, "root.crt")
+
+ // Prepare the URI to the admin endpoint
+ if caID == "" {
+ caID = DefaultCAID
}
- // sanity check, make sure cert file exists first
- _, err := os.Stat(cert)
+ // Determine where we're sending the request to get the CA info
+ adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, configFlag, configAdapterFlag)
if err != nil {
- return caddy.ExitCodeFailedStartup, fmt.Errorf("accessing certificate file: %v", err)
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
}
- err = truststore.UninstallFile(cert,
+ // Fetch the root cert from the admin API
+ rootCert, err := rootCertFromAdmin(adminAddr, caID)
+ if err != nil {
+ return caddy.ExitCodeFailedStartup, err
+ }
+
+ // Uninstall the cert!
+ err = truststore.Uninstall(rootCert,
truststore.WithDebug(),
truststore.WithFirefox(),
truststore.WithJava())
if err != nil {
- return caddy.ExitCodeFailedStartup, err
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to uninstall certificate file: %v", err)
}
return caddy.ExitCodeSuccess, nil
}
+
+// rootCertFromAdmin makes the API request to fetch the
+func rootCertFromAdmin(adminAddr string, caID string) (*x509.Certificate, error) {
+ uri := adminPKICertificatesEndpoint + caID
+
+ // Make the request to fetch the CA info
+ resp, err := caddycmd.AdminAPIRequest(adminAddr, http.MethodGet, uri, make(http.Header), nil)
+ if err != nil {
+ return nil, fmt.Errorf("requesting CA info: %v", err)
+ }
+ defer resp.Body.Close()
+
+ // Decode the resposne
+ caInfo := new(CAInfo)
+ err = json.NewDecoder(resp.Body).Decode(caInfo)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode JSON response: %v", err)
+ }
+
+ // Decode the root
+ rootBlock, _ := pem.Decode([]byte(caInfo.Root))
+ if rootBlock == nil {
+ return nil, fmt.Errorf("failed to decode root certificate: %v", err)
+ }
+ rootCert, err := x509.ParseCertificate(rootBlock.Bytes)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse root certificate: %v", err)
+ }
+
+ return rootCert, nil
+}