Skip to content

Commit

Permalink
Add SSLKEYLOGFILE support to k6
Browse files Browse the repository at this point in the history
fixes #1043
  • Loading branch information
mstoykov committed Apr 8, 2022
1 parent 24aa722 commit 927f4d6
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 4 deletions.
33 changes: 33 additions & 0 deletions cmd/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.k6.io/k6/lib/testutils"
"go.k6.io/k6/lib/testutils/httpmultibin"
)

const (
Expand Down Expand Up @@ -229,6 +230,38 @@ func TestMetricsAndThresholds(t *testing.T) {
require.Equal(t, expected, teardownThresholds)
}

func TestSSLKEYLOGFILE(t *testing.T) {
t.Parallel()

// TODO don't use insecureSkipTLSVerify when/if tlsConfig is given to the runner from outside
tb := httpmultibin.NewHTTPMultiBin(t)
ts := newGlobalTestState(t)
ts.args = []string{"k6", "run", "-"}
ts.envVars = map[string]string{"SSLKEYLOGFILE": "./ssl.log"}
ts.stdIn = bytes.NewReader([]byte(tb.Replacer.Replace(`
import http from "k6/http"
export const options = {
hosts: {
"HTTPSBIN_DOMAIN": "HTTPSBIN_IP",
},
insecureSkipTLSVerify: true,
}
export default () => {
http.get("HTTPSBIN_URL/get");
}
`)))

newRootCommand(ts.globalState).execute()

assert.Empty(t, ts.stdErr.String())
assert.Empty(t, ts.loggerHook.Drain())
sslloglines, err := afero.ReadFile(ts.fs, filepath.Join(ts.cwd, "ssl.log"))
require.NoError(t, err)
// TODO maybe have multiple depending on the ciphers used as that seems to change it
require.Regexp(t, "^CLIENT_[A-Z_]+ [0-9a-f]+ [0-9a-f]+\n", string(sslloglines))
}

// TODO: add a hell of a lot more integration tests, including some that spin up
// a test HTTP server and actually check if k6 hits it

Expand Down
5 changes: 5 additions & 0 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,11 @@ func (c *cmdRun) run(cmd *cobra.Command, args []string) error {
logger.Debug("Waiting for engine processes to finish...")
engineWait()
logger.Debug("Everything has finished, exiting k6!")
if test.keywriter != nil {
if err := test.keywriter.Close(); err != nil {
logger.WithError(err).Warn("error while closing the SSLKEYLOGFILE")
}
}
if interrupt != nil {
return interrupt
}
Expand Down
6 changes: 6 additions & 0 deletions cmd/runtime_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ func getRuntimeOptions(flags *pflag.FlagSet, environment map[string]string) (lib
}
}

if envVar, ok := environment["SSLKEYLOGFILE"]; ok {
if !opts.KeyWriter.Valid {
opts.KeyWriter = null.StringFrom(envVar)
}
}

if opts.IncludeSystemEnvVars.Bool { // If enabled, gather the actual system environment variables
opts.Env = environment
}
Expand Down
52 changes: 48 additions & 4 deletions cmd/test_load.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import (
"archive/tar"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"sync"

"github.com/spf13/afero"
"github.com/spf13/cobra"
Expand All @@ -25,12 +29,14 @@ const (
// and configured k6 test.
type loadedTest struct {
sourceRootPath string // contains the raw string the user supplied
pwd string
source *loader.SourceData
fileSystems map[string]afero.Fs
runtimeOptions lib.RuntimeOptions
metricsRegistry *metrics.Registry
builtInMetrics *metrics.BuiltinMetrics
initRunner lib.Runner // TODO: rename to something more appropriate
keywriter io.Closer

// Only set if cliConfigGetter is supplied to loadAndConfigureTest() or if
// consolidateDeriveAndValidateConfig() is manually called.
Expand All @@ -49,7 +55,7 @@ func loadAndConfigureTest(

sourceRootPath := args[0]
gs.logger.Debugf("Resolving and reading test '%s'...", sourceRootPath)
src, fileSystems, err := readSource(gs, sourceRootPath)
src, fileSystems, pwd, err := readSource(gs, sourceRootPath)
if err != nil {
return nil, err
}
Expand All @@ -67,6 +73,7 @@ func loadAndConfigureTest(

registry := metrics.NewRegistry()
test := &loadedTest{
pwd: pwd,
sourceRootPath: sourceRootPath,
source: src,
fileSystems: fileSystems,
Expand Down Expand Up @@ -106,6 +113,15 @@ func (lt *loadedTest) initializeFirstRunner(gs *globalState) error {
BuiltinMetrics: lt.builtInMetrics,
Registry: lt.metricsRegistry,
}
if lt.runtimeOptions.KeyWriter.Valid {
keyfileName := filepath.Join(lt.pwd, lt.runtimeOptions.KeyWriter.String)
f, err := lt.createFileForAppend(keyfileName)
if err != nil {
return err
}
lt.keywriter = f
state.KeyLogger = &syncWriter{w: f}
}
switch testType {
case testTypeJS:
logger.Debug("Trying to load as a JS test...")
Expand Down Expand Up @@ -145,15 +161,15 @@ func (lt *loadedTest) initializeFirstRunner(gs *globalState) error {

// readSource is a small wrapper around loader.ReadSource returning
// result of the load and filesystems map
func readSource(globalState *globalState, filename string) (*loader.SourceData, map[string]afero.Fs, error) {
func readSource(globalState *globalState, filename string) (*loader.SourceData, map[string]afero.Fs, string, error) {
pwd, err := globalState.getwd()
if err != nil {
return nil, nil, err
return nil, nil, "", err
}

filesystems := loader.CreateFilesystems(globalState.fs)
src, err := loader.ReadSource(globalState.logger, filename, pwd, filesystems, globalState.stdIn)
return src, filesystems, err
return src, filesystems, pwd, err
}

func detectTestType(data []byte) string {
Expand Down Expand Up @@ -206,3 +222,31 @@ func (lt *loadedTest) consolidateDeriveAndValidateConfig(

return nil
}

type syncWriter struct {
w io.Writer
m sync.Mutex
}

func (cw *syncWriter) Write(b []byte) (int, error) {
cw.m.Lock()
defer cw.m.Unlock()
return cw.w.Write(b)
}

func (lt *loadedTest) createFileForAppend(filename string) (io.WriteCloser, error) {
fs := lt.fileSystems["file"]
if ok, err := afero.Exists(fs, filename); !ok {
// This whole parts is because afero has problems creating files with OpenFile.
// And while the latest version fixes those for MemMapFS, it doesn't for OsFs.
if err != nil {
return nil, err
}

if _, err = fs.Create(filename); err != nil { // this unfortunately defaults to 664, but nothing else works
return nil, err
}
}

return fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600)
}
4 changes: 4 additions & 0 deletions js/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ type Runner struct {

console *console
setupData []byte

keylogger io.Writer
}

// New returns a new Runner for the provide source
Expand Down Expand Up @@ -126,6 +128,7 @@ func NewFromBundle(rs *lib.RuntimeState, b *Bundle) (*Runner, error) {
ActualResolver: net.LookupIP,
builtinMetrics: rs.BuiltinMetrics,
registry: rs.Registry,
keylogger: rs.KeyLogger,
}

err = r.SetOptions(r.Bundle.Options)
Expand Down Expand Up @@ -201,6 +204,7 @@ func (r *Runner) newVU(idLocal, idGlobal uint64, samplesOut chan<- metrics.Sampl
MaxVersion: uint16(tlsVersions.Max),
Certificates: certs,
Renegotiation: tls.RenegotiateFreelyAsClient,
KeyLogWriter: r.keylogger,
}
// Follow NameToCertificate in https://pkg.go.dev/crypto/[email protected]#Config, leave this field nil
// when it is empty
Expand Down
3 changes: 3 additions & 0 deletions lib/runtime_state.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package lib

import (
"io"

"github.com/sirupsen/logrus"
"go.k6.io/k6/metrics"
)
Expand All @@ -11,5 +13,6 @@ type RuntimeState struct {
// TODO maybe have a struct `Metrics` with `Registry` and `Builtin` ?
Registry *metrics.Registry
BuiltinMetrics *metrics.BuiltinMetrics
KeyLogger io.Writer
Logger *logrus.Logger
}

0 comments on commit 927f4d6

Please sign in to comment.