diff --git a/cmd/run.go b/cmd/run.go index 27f8555277fb..8eda146ddf7b 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -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 } diff --git a/cmd/runtime_options.go b/cmd/runtime_options.go index 79ca302e7bdc..8884df28a412 100644 --- a/cmd/runtime_options.go +++ b/cmd/runtime_options.go @@ -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 } diff --git a/cmd/test_load.go b/cmd/test_load.go index 97bc263062af..60132a696b72 100644 --- a/cmd/test_load.go +++ b/cmd/test_load.go @@ -4,6 +4,9 @@ import ( "archive/tar" "bytes" "fmt" + "io" + "os" + "sync" "github.com/spf13/afero" "github.com/spf13/cobra" @@ -31,6 +34,7 @@ type loadedTest struct { 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. @@ -103,9 +107,22 @@ func (lt *loadedTest) initializeFirstRunner(gs *globalState) error { switch testType { case testTypeJS: logger.Debug("Trying to load as a JS test...") - runner, err := js.New( - gs.logger, lt.source, lt.fileSystems, lt.runtimeOptions, lt.builtInMetrics, lt.metricsRegistry, - ) + state := &lib.RuntimeState{ + Logger: gs.logger, + RuntimeOptions: <.runtimeOptions, + BuiltinMetrics: lt.builtInMetrics, + Registry: lt.metricsRegistry, + } + if lt.runtimeOptions.KeyWriter.Valid { + fs := lt.fileSystems["file"] + f, err := fs.OpenFile(lt.runtimeOptions.KeyWriter.String, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) + if err != nil { + return err + } + lt.keywriter = f + state.KeyLogger = &syncWriter{w: f} + } + runner, err := js.NewFromState(state, lt.source, lt.fileSystems) // TODO: should we use common.UnwrapGojaInterruptedError() here? if err != nil { return fmt.Errorf("could not load JS test '%s': %w", testPath, err) @@ -202,3 +219,14 @@ 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) +} diff --git a/js/runner.go b/js/runner.go index d26e14777045..cc9f4b2417bf 100644 --- a/js/runner.go +++ b/js/runner.go @@ -79,6 +79,24 @@ type Runner struct { console *console setupData []byte + + keylogger io.Writer +} + +func NewFromState( + rs *lib.RuntimeState, src *loader.SourceData, filesystems map[string]afero.Fs, +) (*Runner, error) { + bundle, err := NewBundle(rs.Logger, src, filesystems, *rs.RuntimeOptions, rs.Registry) + if err != nil { + return nil, err + } + + r, err := NewFromBundle(rs.Logger, bundle, rs.BuiltinMetrics, rs.Registry) + if err != nil { + return nil, err + } + r.keylogger = rs.KeyLogger + return r, nil } // New returns a new Runner for the provide source @@ -207,6 +225,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/tls@go1.17.6#Config, leave this field nil // when it is empty diff --git a/lib/runtime_options.go b/lib/runtime_options.go index b980c0768bca..93c548ed14d2 100644 --- a/lib/runtime_options.go +++ b/lib/runtime_options.go @@ -60,6 +60,7 @@ type RuntimeOptions struct { NoThresholds null.Bool `json:"noThresholds"` NoSummary null.Bool `json:"noSummary"` SummaryExport null.String `json:"summaryExport"` + KeyWriter null.String `json:"-"` } // ValidateCompatibilityMode checks if the provided val is a valid compatibility mode