From 244b839f9813ae68c5527e6aadadaff0642c1a00 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 31 Mar 2020 17:56:36 -0600 Subject: [PATCH] pki: Add trust subcommand to install root cert (closes #3204) --- caddy.go | 2 +- modules/caddypki/ca.go | 22 +++++++++++++++++++ modules/caddypki/command.go | 44 +++++++++++++++++++++++++++++++++++++ modules/caddypki/pki.go | 22 +++---------------- storage.go | 3 +++ 5 files changed, 73 insertions(+), 20 deletions(-) diff --git a/caddy.go b/caddy.go index 1f9018a1..00a56e74 100644 --- a/caddy.go +++ b/caddy.go @@ -372,7 +372,7 @@ func run(newCfg *Config, start bool) error { } if newCfg.storage == nil { - newCfg.storage = &certmagic.FileStorage{Path: AppDataDir()} + newCfg.storage = DefaultStorage } certmagic.Default.Storage = newCfg.storage diff --git a/modules/caddypki/ca.go b/modules/caddypki/ca.go index f15883e1..21a8bd50 100644 --- a/modules/caddypki/ca.go +++ b/modules/caddypki/ca.go @@ -24,6 +24,7 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/certmagic" + "github.com/smallstep/truststore" "go.uber.org/zap" ) @@ -323,6 +324,27 @@ func (ca CA) newReplacer() *caddy.Replacer { return repl } +// installRoot installs this CA's root certificate into the +// local trust store(s) if it is not already trusted. The CA +// must already be provisioned. +func (ca CA) installRoot() error { + // avoid password prompt if already trusted + if trusted(ca.root) { + ca.log.Info("root certificate is already trusted by system", + zap.String("path", ca.rootCertPath)) + return nil + } + + ca.log.Warn("installing root certificate (you might be prompted for password)", + zap.String("path", ca.rootCertPath)) + + return truststore.Install(ca.root, + truststore.WithDebug(), + truststore.WithFirefox(), + truststore.WithJava(), + ) +} + const ( defaultCAID = "local" defaultCAName = "Caddy Local Authority" diff --git a/modules/caddypki/command.go b/modules/caddypki/command.go index 9276fcb5..9117f3f0 100644 --- a/modules/caddypki/command.go +++ b/modules/caddypki/command.go @@ -15,6 +15,7 @@ package caddypki import ( + "context" "flag" "fmt" "os" @@ -26,6 +27,25 @@ import ( ) func init() { + caddycmd.RegisterCommand(caddycmd.Command{ + Name: "trust", + Func: cmdTrust, + 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.`, + }) + caddycmd.RegisterCommand(caddycmd.Command{ Name: "untrust", Func: cmdUntrust, @@ -57,6 +77,30 @@ If no flags are specified, --ca=local is assumed.`, }) } +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() + + // provision the CA, which generates and stores a root + // certificate if one doesn't already exist in storage + ca := CA{ + storage: caddy.DefaultStorage, + } + err := ca.Provision(ctx, defaultCAID, caddy.Log()) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + err = ca.installRoot() + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + return caddy.ExitCodeSuccess, nil +} + func cmdUntrust(fs caddycmd.Flags) (int, error) { ca := fs.String("ca") cert := fs.String("cert") diff --git a/modules/caddypki/pki.go b/modules/caddypki/pki.go index 562fae2e..f9aa372d 100644 --- a/modules/caddypki/pki.go +++ b/modules/caddypki/pki.go @@ -18,7 +18,6 @@ import ( "fmt" "github.com/caddyserver/caddy/v2" - "github.com/smallstep/truststore" "go.uber.org/zap" ) @@ -71,30 +70,15 @@ func (p *PKI) Start() error { // install roots to trust store, if not disabled for _, ca := range p.CAs { if ca.InstallTrust != nil && !*ca.InstallTrust { - ca.log.Warn("root certificate trust store installation disabled; local clients may show warnings", + ca.log.Warn("root certificate trust store installation disabled; unconfigured clients may show warnings", zap.String("path", ca.rootCertPath)) continue } - // avoid password prompt if already trusted - if trusted(ca.root) { - ca.log.Info("root certificate is already trusted by system", - zap.String("path", ca.rootCertPath)) - continue - } - - ca.log.Warn("trusting root certificate (you might be prompted for password)", - zap.String("path", ca.rootCertPath)) - - err := truststore.Install(ca.root, - truststore.WithDebug(), - truststore.WithFirefox(), - truststore.WithJava(), - ) - if err != nil { + if err := ca.installRoot(); err != nil { // could be some system dependencies that are missing; // shouldn't totally prevent startup, but we should log it - p.log.Error("failed to install root certificate", + ca.log.Error("failed to install root certificate", zap.Error(err), zap.String("certificate_file", ca.rootCertPath)) } diff --git a/storage.go b/storage.go index f227269c..62f9b1c6 100644 --- a/storage.go +++ b/storage.go @@ -155,3 +155,6 @@ func AppDataDir() string { // ConfigAutosavePath is the default path to which the last config will be persisted. var ConfigAutosavePath = filepath.Join(AppConfigDir(), "autosave.json") + +// DefaultStorage is Caddy's default storage module. +var DefaultStorage = &certmagic.FileStorage{Path: AppDataDir()}