plugin/tls: Add the keylog option to configure TLSConfig.KeyLogWriter (#7537)

* tls: Add the keylog option to configure TLSConfig.KeyLogWriter

Signed-off-by: Ilya Kulakov <kulakov.ilya@gmail.com>

* tls: Close keylog file on instance shutdown.

Signed-off-by: Ilya Kulakov <kulakov.ilya@gmail.com>

---------

Signed-off-by: Ilya Kulakov <kulakov.ilya@gmail.com>
This commit is contained in:
Ilya Kulakov
2026-03-27 12:10:13 -07:00
committed by GitHub
parent 471d62926d
commit a8caf4c375
3 changed files with 74 additions and 0 deletions

View File

@@ -27,6 +27,7 @@ Parameter CA is optional. If not set, system CAs can be used to verify the clien
~~~ txt
tls CERT KEY [CA] {
client_auth nocert|request|require|verify_if_given|require_and_verify
keylog FILE
}
~~~
@@ -35,6 +36,9 @@ The option value corresponds to the [ClientAuthType values of the Go tls package
The default is "nocert". Note that it makes no sense to specify parameter CA unless this option is
set to verify\_if\_given or require\_and\_verify.
The keylog can be specified to export TLS master secrets in key log format to allow external programs
to decrypt TLS connections. It compromises security and should only be used for debugging!
## Examples
Start a DNS-over-TLS server that picks up incoming DNS-over-TLS queries on port 5553 and uses the

View File

@@ -2,14 +2,18 @@ package tls
import (
ctls "crypto/tls"
"os"
"path/filepath"
"github.com/coredns/caddy"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin"
clog "github.com/coredns/coredns/plugin/pkg/log"
"github.com/coredns/coredns/plugin/pkg/tls"
)
var log = clog.NewWithPlugin("tls")
func init() { plugin.Register("tls", setup) }
func setup(c *caddy.Controller) error {
@@ -33,6 +37,7 @@ func parseTLS(c *caddy.Controller) error {
return plugin.Error("tls", c.ArgErr())
}
clientAuth := ctls.NoClientCert
var keyLog string
for c.NextBlock() {
switch c.Val() {
case "client_auth":
@@ -54,6 +59,15 @@ func parseTLS(c *caddy.Controller) error {
default:
return c.Errf("unknown authentication type '%s'", authTypeArgs[0])
}
case "keylog":
args := c.RemainingArgs()
if len(args) != 1 {
return c.ArgErr()
}
keyLog = args[0]
if !filepath.IsAbs(keyLog) && config.Root != "" {
keyLog = filepath.Join(config.Root, keyLog)
}
default:
return c.Errf("unknown option '%s'", c.Val())
}
@@ -71,6 +85,23 @@ func parseTLS(c *caddy.Controller) error {
// NewTLSConfigFromArgs only sets RootCAs, so we need to let ClientCAs refer to it.
tls.ClientCAs = tls.RootCAs
if len(keyLog) > 0 {
absKeyLog, err := filepath.Abs(keyLog)
if err != nil {
return c.Errf("unable to write TLS Key Log to %q: %s", keyLog, err)
}
f, err := os.OpenFile(absKeyLog, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
if err != nil {
return c.Errf("unable to write TLS Key Log to %q: %s", absKeyLog, err)
}
c.OnShutdown(func() error {
f.Close()
return nil
})
tls.KeyLogWriter = f
log.Warningf("Writing TLS Key Log to %q\n", absKeyLog)
}
config.TLSConfig = tls
}
return nil

View File

@@ -2,6 +2,8 @@ package tls
import (
"crypto/tls"
"os"
"path/filepath"
"strings"
"testing"
@@ -23,6 +25,7 @@ func TestTLS(t *testing.T) {
{"tls test_cert.pem test_key.pem test_ca.pem {\nclient_auth require\n}", false, "", ""},
{"tls test_cert.pem test_key.pem test_ca.pem {\nclient_auth verify_if_given\n}", false, "", ""},
{"tls test_cert.pem test_key.pem test_ca.pem {\nclient_auth require_and_verify\n}", false, "", ""},
{"tls test_cert.pem test_key.pem test_ca.pem {\nkeylog tls.log\n}", false, "", ""},
// negative
{"tls test_cert.pem test_key.pem test_ca.pem {\nunknown\n}", true, "", "unknown option"},
// client_auth takes exactly one parameter, which must be one of known keywords.
@@ -85,3 +88,39 @@ func TestTLSClientAuthentication(t *testing.T) {
}
}
}
func TestTLSKeyLog(t *testing.T) {
t.Run("No Path", func(t *testing.T) {
input := "tls test_cert.pem test_key.pem test_ca.pem {\nkeylog\n}"
c := caddy.NewTestController("dns", input)
err := setup(c)
if err == nil {
t.Error("Expected error but found none")
}
})
t.Run("Bad Path", func(t *testing.T) {
tmpDir := t.TempDir()
os.Chmod(tmpDir, 0000)
input := "tls test_cert.pem test_key.pem test_ca.pem {\nkeylog " + filepath.Join(tmpDir, "tls.log") + "\n}"
c := caddy.NewTestController("dns", input)
err := setup(c)
if err == nil {
t.Error("Expected error but found none")
}
})
t.Run("Good Path", func(t *testing.T) {
tmpDir := t.TempDir()
input := "tls test_cert.pem test_key.pem test_ca.pem {\nkeylog " + filepath.Join(tmpDir, "tls.log") + "\n}"
c := caddy.NewTestController("dns", input)
err := setup(c)
if err != nil {
t.Errorf("Expected no error but found %v", err)
}
cfg := dnsserver.GetConfig(c)
if cfg.TLSConfig.KeyLogWriter == nil {
t.Fatal("KeyLogWriter is not set")
}
})
}