caddyhttp: Support TLS key logging for debugging (#4808)

* Add SSL key logging.

* Resolve merge conflict with master

* Add Caddyfile support; various fixes

* Also commit go.mod and go.sum, oops

* Appease linter

* Minor tweaks

* Add doc comment

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
This commit is contained in:
David Manouchehri 2022-09-16 16:05:37 -04:00 committed by GitHub
parent 74547f5bed
commit 616418281b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 40 deletions

View File

@ -53,8 +53,7 @@ func init() {
// parseBind parses the bind directive. Syntax: // parseBind parses the bind directive. Syntax:
// //
// bind <addresses...> // bind <addresses...>
//
func parseBind(h Helper) ([]ConfigValue, error) { func parseBind(h Helper) ([]ConfigValue, error) {
var lnHosts []string var lnHosts []string
for h.Next() { for h.Next() {
@ -65,28 +64,28 @@ func parseBind(h Helper) ([]ConfigValue, error) {
// parseTLS parses the tls directive. Syntax: // parseTLS parses the tls directive. Syntax:
// //
// tls [<email>|internal]|[<cert_file> <key_file>] { // tls [<email>|internal]|[<cert_file> <key_file>] {
// protocols <min> [<max>] // protocols <min> [<max>]
// ciphers <cipher_suites...> // ciphers <cipher_suites...>
// curves <curves...> // curves <curves...>
// client_auth { // client_auth {
// mode [request|require|verify_if_given|require_and_verify] // mode [request|require|verify_if_given|require_and_verify]
// trusted_ca_cert <base64_der> // trusted_ca_cert <base64_der>
// trusted_ca_cert_file <filename> // trusted_ca_cert_file <filename>
// trusted_leaf_cert <base64_der> // trusted_leaf_cert <base64_der>
// trusted_leaf_cert_file <filename> // trusted_leaf_cert_file <filename>
// } // }
// alpn <values...> // alpn <values...>
// load <paths...> // load <paths...>
// ca <acme_ca_endpoint> // ca <acme_ca_endpoint>
// ca_root <pem_file> // ca_root <pem_file>
// dns <provider_name> [...] // dns <provider_name> [...]
// on_demand // on_demand
// eab <key_id> <mac_key> // eab <key_id> <mac_key>
// issuer <module_name> [...] // issuer <module_name> [...]
// get_certificate <module_name> [...] // get_certificate <module_name> [...]
// } // insecure_secrets_log <log_file>
// // }
func parseTLS(h Helper) ([]ConfigValue, error) { func parseTLS(h Helper) ([]ConfigValue, error) {
cp := new(caddytls.ConnectionPolicy) cp := new(caddytls.ConnectionPolicy)
var fileLoader caddytls.FileLoader var fileLoader caddytls.FileLoader
@ -396,6 +395,12 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
} }
onDemand = true onDemand = true
case "insecure_secrets_log":
if !h.NextArg() {
return nil, h.ArgErr()
}
cp.InsecureSecretsLog = h.Val()
default: default:
return nil, h.Errf("unknown subdirective: %s", h.Val()) return nil, h.Errf("unknown subdirective: %s", h.Val())
} }
@ -516,8 +521,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
// parseRoot parses the root directive. Syntax: // parseRoot parses the root directive. Syntax:
// //
// root [<matcher>] <path> // root [<matcher>] <path>
//
func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) { func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
var root string var root string
for h.Next() { for h.Next() {
@ -695,12 +699,11 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
// parseLog parses the log directive. Syntax: // parseLog parses the log directive. Syntax:
// //
// log { // log {
// output <writer_module> ... // output <writer_module> ...
// format <encoder_module> ... // format <encoder_module> ...
// level <level> // level <level>
// } // }
//
func parseLog(h Helper) ([]ConfigValue, error) { func parseLog(h Helper) ([]ConfigValue, error) {
return parseLogHelper(h, nil) return parseLogHelper(h, nil)
} }

View File

@ -421,13 +421,13 @@ func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ any) (any, error) {
// parseLogOptions parses the global log option. Syntax: // parseLogOptions parses the global log option. Syntax:
// //
// log [name] { // log [name] {
// output <writer_module> ... // output <writer_module> ...
// format <encoder_module> ... // format <encoder_module> ...
// level <level> // level <level>
// include <namespaces...> // include <namespaces...>
// exclude <namespaces...> // exclude <namespaces...>
// } // }
// //
// When the name argument is unspecified, this directive modifies the default // When the name argument is unspecified, this directive modifies the default
// logger. // logger.

View File

@ -20,11 +20,14 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/mholt/acmez" "github.com/mholt/acmez"
"go.uber.org/zap"
) )
func init() { func init() {
@ -156,6 +159,16 @@ type ConnectionPolicy struct {
// is no policy configured for the empty SNI value. // is no policy configured for the empty SNI value.
DefaultSNI string `json:"default_sni,omitempty"` DefaultSNI string `json:"default_sni,omitempty"`
// Also known as "SSLKEYLOGFILE", TLS secrets will be written to
// this file in NSS key log format which can then be parsed by
// Wireshark and other tools. This is INSECURE as it allows other
// programs or tools to decrypt TLS connections. However, this
// capability can be useful for debugging and troubleshooting.
// **ENABLING THIS LOG COMPROMISES SECURITY!**
//
// This feature is EXPERIMENTAL and subject to change or removal.
InsecureSecretsLog string `json:"insecure_secrets_log,omitempty"`
// TLSConfig is the fully-formed, standard lib TLS config // TLSConfig is the fully-formed, standard lib TLS config
// used to serve TLS connections. Provision all // used to serve TLS connections. Provision all
// ConnectionPolicies to populate this. It is exported only // ConnectionPolicies to populate this. It is exported only
@ -280,6 +293,30 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
} }
} }
if p.InsecureSecretsLog != "" {
filename, err := caddy.NewReplacer().ReplaceOrErr(p.InsecureSecretsLog, true, true)
if err != nil {
return err
}
filename, err = filepath.Abs(filename)
if err != nil {
return err
}
logFile, _, err := secretsLogPool.LoadOrNew(filename, func() (caddy.Destructor, error) {
w, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
return destructableWriter{w}, err
})
if err != nil {
return err
}
ctx.OnCancel(func() { _, _ = secretsLogPool.Delete(filename) })
cfg.KeyLogWriter = logFile.(io.Writer)
tlsApp.logger.Warn("TLS SECURITY COMPROMISED: secrets logging is enabled!",
zap.String("log_filename", filename))
}
setDefaultTLSParams(cfg) setDefaultTLSParams(cfg)
p.TLSConfig = cfg p.TLSConfig = cfg
@ -297,7 +334,8 @@ func (p ConnectionPolicy) SettingsEmpty() bool {
p.ProtocolMin == "" && p.ProtocolMin == "" &&
p.ProtocolMax == "" && p.ProtocolMax == "" &&
p.ClientAuthentication == nil && p.ClientAuthentication == nil &&
p.DefaultSNI == "" p.DefaultSNI == "" &&
p.InsecureSecretsLog == ""
} }
// ClientAuthentication configures TLS client auth. // ClientAuthentication configures TLS client auth.
@ -542,3 +580,9 @@ type ClientCertificateVerifier interface {
} }
var defaultALPN = []string{"h2", "http/1.1"} var defaultALPN = []string{"h2", "http/1.1"}
type destructableWriter struct{ *os.File }
func (d destructableWriter) Destruct() error { return d.Close() }
var secretsLogPool = caddy.NewUsagePool()