Repository: coreruleset/go-ftw
Pull request #123 (2022-12-01 22:57:47)

feat: move config to parameter instead of global
Signed-off-by: Felipe Zipitria <felipe.zipitria@owasp.org>

Removes global config. Now some methods will receive the extra config parameter.

Partially addresses #108.

theseion: Awesome!

Patch:
diff --git a/check/base.go b/check/base.go
index d0995f5..32c9836 100644
--- a/check/base.go
+++ b/check/base.go
@@ -10,21 +10,19 @@ import (
 
 // FTWCheck is the base struct for checking test results
 type FTWCheck struct {
-	log       *waflog.FTWLogLines
-	expected  *test.Output
-	overrides *config.FTWTestOverride
+	log      *waflog.FTWLogLines
+	expected *test.Output
+	cfg      *config.FTWConfiguration
 }
 
 // NewCheck creates a new FTWCheck, allowing to inject the configuration
 func NewCheck(c *config.FTWConfiguration) *FTWCheck {
+	//TODO: check error
+	ll, _ := waflog.NewFTWLogLines(c)
 	check := &FTWCheck{
-		log: &waflog.FTWLogLines{
-			FileName:    c.LogFile,
-			StartMarker: nil,
-			EndMarker:   nil,
-		},
-		expected:  &test.Output{},
-		overrides: &c.TestOverride,
+		log:      ll,
+		cfg:      c,
+		expected: &test.Output{},
 	}
 
 	return check
@@ -62,7 +60,7 @@ func (c *FTWCheck) SetNoLogContains(contains string) {
 
 // ForcedIgnore check if this id need to be ignored from results
 func (c *FTWCheck) ForcedIgnore(id string) bool {
-	for re := range c.overrides.Ignore {
+	for re := range c.cfg.TestOverride.Ignore {
 		if re.MatchString(id) {
 			return true
 		}
@@ -72,7 +70,7 @@ func (c *FTWCheck) ForcedIgnore(id string) bool {
 
 // ForcedPass check if this id need to be ignored from results
 func (c *FTWCheck) ForcedPass(id string) bool {
-	for re := range c.overrides.ForcePass {
+	for re := range c.cfg.TestOverride.ForcePass {
 		if re.MatchString(id) {
 			return true
 		}
@@ -82,7 +80,7 @@ func (c *FTWCheck) ForcedPass(id string) bool {
 
 // ForcedFail check if this id need to be ignored from results
 func (c *FTWCheck) ForcedFail(id string) bool {
-	for re := range c.overrides.ForceFail {
+	for re := range c.cfg.TestOverride.ForceFail {
 		if re.MatchString(id) {
 			return true
 		}
@@ -92,7 +90,7 @@ func (c *FTWCheck) ForcedFail(id string) bool {
 
 // CloudMode returns true if we are running in cloud mode
 func (c *FTWCheck) CloudMode() bool {
-	return config.FTWConfig.RunMode == config.CloudRunMode
+	return c.cfg.RunMode == config.CloudRunMode
 }
 
 // SetCloudMode alters the values for expected logs and status code
diff --git a/cmd/root.go b/cmd/root.go
index 6b3a36a..b28a732 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -17,6 +17,8 @@ var (
 	cloud   bool
 )
 
+var cfg = config.NewDefaultConfig()
+
 // rootCmd represents the base command when called without any subcommands
 var rootCmd = &cobra.Command{
 	Use:   "ftw run",
@@ -52,14 +54,15 @@ func initConfig() {
 	if trace {
 		zerolog.SetGlobalLevel(zerolog.TraceLevel)
 	}
-	errFile := config.NewConfigFromFile(cfgFile)
+	cfg, errFile := config.NewConfigFromFile(cfgFile)
 	if errFile != nil {
-		errEnv := config.NewConfigFromEnv()
+		cfgenv, errEnv := config.NewConfigFromEnv()
 		if errEnv != nil {
 			log.Fatalf("cannot read config from file (%s) nor environment (%s).", errFile.Error(), errEnv.Error())
 		}
+		cfg = cfgenv
 	}
 	if cloud {
-		config.FTWConfig.RunMode = config.CloudRunMode
+		cfg.RunMode = config.CloudRunMode
 	}
 }
diff --git a/cmd/run.go b/cmd/run.go
index 23504d6..a58a019 100644
--- a/cmd/run.go
+++ b/cmd/run.go
@@ -64,7 +64,7 @@ var runCmd = &cobra.Command{
 		out := output.NewOutput(wantedOutput, os.Stdout)
 		_ = out.Println("%s", out.Message("** Starting tests!"))
 
-		currentRun, err := runner.Run(tests, runner.Config{
+		currentRun, err := runner.Run(cfg, tests, runner.RunnerConfig{
 			Include:           includeRE,
 			Exclude:           excludeRE,
 			ShowTime:          showTime,
diff --git a/config/config.go b/config/config.go
index 2d6ea9e..5a98e84 100644
--- a/config/config.go
+++ b/config/config.go
@@ -11,15 +11,35 @@ import (
 	"github.com/knadh/koanf/providers/rawbytes"
 )
 
+// NewConfig creates a new configuration with the passed parameters
+func NewConfig(logFile string, overrides FTWTestOverride, logMarkerHeaderName string, runMode RunMode) *FTWConfiguration {
+	cfg := &FTWConfiguration{
+		LogFile:             logFile,
+		TestOverride:        overrides,
+		LogMarkerHeaderName: logMarkerHeaderName,
+		RunMode:             runMode,
+	}
+	return cfg
+}
+
+// NewDefaultConfig initializes the configuration with default values
+func NewDefaultConfig() *FTWConfiguration {
+	cfg := &FTWConfiguration{
+		LogFile:             "",
+		TestOverride:        FTWTestOverride{},
+		LogMarkerHeaderName: DefaultLogMarkerHeaderName,
+		RunMode:             DefaultRunMode,
+	}
+	return cfg
+}
+
 // NewConfigFromFile reads configuration information from the config file if it exists,
 // or uses `.ftw.yaml` as default file
-func NewConfigFromFile(cfgFile string) error {
-	// koanf merges by default, but we never want to merge in this case
-	Reset()
-
+func NewConfigFromFile(cfgFile string) (*FTWConfiguration, error) {
 	// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
 	var k = koanf.New(".")
 	var err error
+	cfg := NewDefaultConfig()
 
 	// first check if we had an explicit call with config file
 	if cfgFile == "" {
@@ -33,7 +53,7 @@ func NewConfigFromFile(cfgFile string) error {
 
 		home, err = os.UserHomeDir()
 		if err != nil { // home folder could not be retrieved
-			return err
+			return nil, err
 		}
 
 		cfgFile = home + "/.ftw.yaml"
@@ -42,32 +62,29 @@ func NewConfigFromFile(cfgFile string) error {
 
 	_, err = os.Stat(cfgFile)
 	if err != nil { // file exists, so we read it looking for config values
-		return err
+		return nil, err
 	}
 
 	err = k.Load(file.Provider(cfgFile), yaml.Parser())
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	// At this point we have loaded our config, now we need to
 	// unmarshal the whole root module
-	err = k.UnmarshalWithConf("", &FTWConfig, koanf.UnmarshalConf{Tag: "koanf"})
+	err = k.UnmarshalWithConf("", cfg, koanf.UnmarshalConf{Tag: "koanf"})
 	if err != nil {
-		return err
+		return nil, err
 	}
-	loadDefaults()
 
-	return err
+	return cfg, err
 }
 
 // NewConfigFromEnv reads configuration information from environment variables that start with `FTW_`
-func NewConfigFromEnv() error {
-	// koanf merges by default, but we never want to merge in this case
-	Reset()
-
+func NewConfigFromEnv() (*FTWConfiguration, error) {
 	var err error
 	var k = koanf.New(".")
+	cfg := NewDefaultConfig()
 
 	err = k.Load(env.Provider("FTW_", ".", func(s string) string {
 		return strings.ReplaceAll(strings.ToLower(
@@ -75,50 +92,47 @@ func NewConfigFromEnv() error {
 	}), nil)
 
 	if err != nil {
-		return err
+		return nil, err
 	}
 	// Unmarshal the whole root module
-	err = k.UnmarshalWithConf("", &FTWConfig, koanf.UnmarshalConf{Tag: "koanf"})
-	loadDefaults()
+	err = k.UnmarshalWithConf("", cfg, koanf.UnmarshalConf{Tag: "koanf"})
 
-	return err
+	return cfg, err
 }
 
 // NewConfigFromString initializes the configuration from a yaml formatted string. Useful for testing.
-func NewConfigFromString(conf string) error {
-	// koanf merges by default, but we never want to merge in this case
-	Reset()
-
+func NewConfigFromString(conf string) (*FTWConfiguration, error) {
 	var k = koanf.New(".")
 	var err error
+	cfg := NewDefaultConfig()
 
 	err = k.Load(rawbytes.Provider([]byte(conf)), yaml.Parser())
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	// Unmarshal the whole root module
-	err = k.UnmarshalWithConf("", &FTWConfig, koanf.UnmarshalConf{Tag: "koanf"})
-	loadDefaults()
+	err = k.UnmarshalWithConf("", cfg, koanf.UnmarshalConf{Tag: "koanf"})
 
-	return err
+	return cfg, err
 }
 
-// Reset configuration to uninitialized state
-func Reset() {
-	FTWConfig = nil
+// WithLogfile changes the logfile in the configuration.
+func (c *FTWConfiguration) WithLogfile(logfile string) {
+	c.LogFile = logfile
 }
 
-func loadDefaults() {
-	// Note: koanf has a way to set defaults. However, koanf's merge behavior
-	// will overwrite defaults when the associated field is empty in nested
-	// structures (top level would work). That's why we set defaults here
-	// explicitly.
-	if FTWConfig.LogMarkerHeaderName == "" {
-		FTWConfig.LogMarkerHeaderName = DefaultLogMarkerHeaderName
-	}
-	if FTWConfig.RunMode == "" {
-		FTWConfig.RunMode = DefaultRunMode
-	}
+// WithOverrides sets the overrides in the configuration.
+func (c *FTWConfiguration) WithOverrides(overrides FTWTestOverride) {
+	c.TestOverride = overrides
+}
+
+// WithRunMode sets the RunMode.
+func (c *FTWConfiguration) WithRunMode(runMode RunMode) {
+	c.RunMode = runMode
+}
 
+// WithLogMarkerHeaderName sets the new LogMarker header name.
+func (c *FTWConfiguration) WithLogMarkerHeaderName(name string) {
+	c.LogMarkerHeaderName = name
 }
diff --git a/config/types.go b/config/types.go
index 1689f0a..6e15e2b 100644
--- a/config/types.go
+++ b/config/types.go
@@ -19,9 +19,6 @@ const (
 	DefaultLogMarkerHeaderName string = "X-CRS-Test"
 )
 
-// FTWConfig is being exported to be used across the app
-var FTWConfig *FTWConfiguration
-
 // FTWConfiguration FTW global Configuration
 type FTWConfiguration struct {
 	LogFile             string          `koanf:"logfile"`
diff --git a/runner/run.go b/runner/run.go
index 7a483eb..dafb885 100644
--- a/runner/run.go
+++ b/runner/run.go
@@ -6,11 +6,12 @@ import (
 	"regexp"
 	"time"
 
+	"github.com/coreruleset/go-ftw/config"
+
 	"github.com/google/uuid"
 	"github.com/rs/zerolog/log"
 
 	"github.com/coreruleset/go-ftw/check"
-	"github.com/coreruleset/go-ftw/config"
 	"github.com/coreruleset/go-ftw/ftwhttp"
 	"github.com/coreruleset/go-ftw/output"
 	"github.com/coreruleset/go-ftw/test"
@@ -21,10 +22,10 @@ import (
 var errBadTestRequest = errors.New("ftw/run: bad test: choose between data, encoded_request, or raw_request")
 
 // Run runs your tests with the specified Config.
-func Run(tests []test.FTWTest, c Config, out *output.Output) (*TestRunContext, error) {
+func Run(cfg *config.FTWConfiguration, tests []test.FTWTest, c RunnerConfig, out *output.Output) (*TestRunContext, error) {
 	out.Println("%s", out.Message("** Running go-ftw!"))
 
-	logLines, err := waflog.NewFTWLogLines(waflog.WithLogFile(config.FTWConfig.LogFile))
+	logLines, err := waflog.NewFTWLogLines(cfg)
 	if err != nil {
 		return &TestRunContext{}, err
 	}
@@ -50,7 +51,8 @@ func Run(tests []test.FTWTest, c Config, out *output.Output) (*TestRunContext, e
 	if maxMarkerLogLines == 0 {
 		maxMarkerLogLines = 500
 	}
-	runContext := TestRunContext{
+	runContext := &TestRunContext{
+		Config:            cfg,
 		Include:           c.Include,
 		Exclude:           c.Exclude,
 		ShowTime:          c.ShowTime,
@@ -61,11 +63,10 @@ func Run(tests []test.FTWTest, c Config, out *output.Output) (*TestRunContext, e
 		Stats:             NewRunStats(),
 		Client:            client,
 		LogLines:          logLines,
-		RunMode:           config.FTWConfig.RunMode,
 	}
 
 	for _, tc := range tests {
-		if err := RunTest(&runContext, tc); err != nil {
+		if err := RunTest(runContext, tc); err != nil {
 			return &TestRunContext{}, err
 		}
 	}
@@ -74,7 +75,7 @@ func Run(tests []test.FTWTest, c Config, out *output.Output) (*TestRunContext, e
 
 	defer cleanLogs(logLines)
 
-	return &runContext, nil
+	return runContext, nil
 }
 
 // RunTest runs an individual test.
@@ -103,7 +104,7 @@ func RunTest(runContext *TestRunContext, ftwTest test.FTWTest) error {
 		}
 		// Iterate over stages
 		for _, stage := range testCase.Stages {
-			ftwCheck := check.NewCheck(config.FTWConfig)
+			ftwCheck := check.NewCheck(runContext.Config)
 			if err := RunStage(runContext, ftwCheck, testCase, stage.Stage); err != nil {
 				return err
 			}
@@ -123,7 +124,7 @@ func RunStage(runContext *TestRunContext, ftwCheck *check.FTWCheck, testCase tes
 	stageID := uuid.NewString()
 	// Apply global overrides initially
 	testRequest := stage.Input
-	err := applyInputOverride(&testRequest)
+	err := applyInputOverride(runContext.Config.TestOverride, &testRequest)
 	if err != nil {
 		log.Debug().Msgf("ftw/run: problem overriding input: %s", err.Error())
 	}
@@ -216,10 +217,10 @@ func markAndFlush(runContext *TestRunContext, dest *ftwhttp.Destination, stageID
 	}
 
 	headers := &ftwhttp.Header{
-		"Accept":                             "*/*",
-		"User-Agent":                         "go-ftw test agent",
-		"Host":                               "localhost",
-		config.FTWConfig.LogMarkerHeaderName: stageID,
+		"Accept":                              "*/*",
+		"User-Agent":                          "go-ftw test agent",
+		"Host":                                "localhost",
+		runContext.Config.LogMarkerHeaderName: stageID,
 	}
 
 	req := ftwhttp.NewRequest(rline, *headers, nil, true)
@@ -240,7 +241,7 @@ func markAndFlush(runContext *TestRunContext, dest *ftwhttp.Destination, stageID
 			return marker, nil
 		}
 	}
-	return nil, fmt.Errorf("can't find log marker. Am I reading the correct log? Log file: %s", runContext.LogLines.FileName)
+	return nil, fmt.Errorf("can't find log marker. Am I reading the correct log? Log file: %s", runContext.Config.LogFile)
 }
 
 func needToSkipTest(include *regexp.Regexp, exclude *regexp.Regexp, title string, enabled bool) bool {
@@ -388,8 +389,8 @@ func getRequestFromTest(testRequest test.Input) *ftwhttp.Request {
 }
 
 // applyInputOverride will check if config had global overrides and write that into the test.
-func applyInputOverride(testRequest *test.Input) error {
-	overrides := config.FTWConfig.TestOverride.Input
+func applyInputOverride(o config.FTWTestOverride, testRequest *test.Input) error {
+	overrides := o.Input
 	if overrides.Port != nil {
 		testRequest.Port = overrides.Port
 	}
diff --git a/runner/types.go b/runner/types.go
index a22b9e2..6e9e2a7 100644
--- a/runner/types.go
+++ b/runner/types.go
@@ -11,8 +11,8 @@ import (
 	"github.com/coreruleset/go-ftw/waflog"
 )
 
-// Config provides configuration for the test runner.
-type Config struct {
+// RunnerConfig provides configuration for the test runner.
+type RunnerConfig struct {
 	// Include is a regular expression to filter tests to include. If nil, all tests are included.
 	Include *regexp.Regexp
 	// Exclude is a regular expression to filter tests to exclude. If nil, no tests are excluded.
@@ -37,6 +37,7 @@ type Config struct {
 // This includes configuration information as well as statistics
 // and results.
 type TestRunContext struct {
+	Config            *config.FTWConfiguration
 	Include           *regexp.Regexp
 	Exclude           *regexp.Regexp
 	ShowTime          bool
@@ -49,5 +50,4 @@ type TestRunContext struct {
 	Duration          time.Duration
 	Client            *ftwhttp.Client
 	LogLines          *waflog.FTWLogLines
-	RunMode           config.RunMode
 }
diff --git a/waflog/read.go b/waflog/read.go
index fb3e664..6511162 100644
--- a/waflog/read.go
+++ b/waflog/read.go
@@ -4,13 +4,10 @@ import (
 	"bytes"
 	"errors"
 	"io"
-	"os"
 	"regexp"
 
 	"github.com/icza/backscanner"
 	"github.com/rs/zerolog/log"
-
-	"github.com/coreruleset/go-ftw/config"
 )
 
 // Contains looks in logfile for regex
@@ -38,10 +35,6 @@ func (ll *FTWLogLines) Contains(match string) bool {
 func (ll *FTWLogLines) getMarkedLines() [][]byte {
 	var found [][]byte
 
-	if err := ll.openLogFile(); err != nil {
-		log.Error().Caller().Msgf("cannot open log file: %s", err)
-	}
-
 	fi, err := ll.logFile.Stat()
 	if err != nil {
 		log.Error().Caller().Msgf("cannot read file's size")
@@ -84,7 +77,7 @@ func (ll *FTWLogLines) getMarkedLines() [][]byte {
 // stageID is the ID of the current stage, which is part of the marker line
 // readLimit is the maximum numbers of lines to check
 func (ll *FTWLogLines) CheckLogForMarker(stageID string, readLimit int) []byte {
-	offset, err := ll.logFile.Seek(0, os.SEEK_END)
+	offset, err := ll.logFile.Seek(0, io.SeekEnd)
 	if err != nil {
 		log.Error().Caller().Err(err).Msgf("failed to seek end of log file")
 		return nil
@@ -96,7 +89,7 @@ func (ll *FTWLogLines) CheckLogForMarker(stageID string, readLimit int) []byte {
 	}
 	scanner := backscanner.NewOptions(ll.logFile, int(offset), backscannerOptions)
 	stageIDBytes := []byte(stageID)
-	crsHeaderBytes := bytes.ToLower([]byte(config.FTWConfig.LogMarkerHeaderName))
+	crsHeaderBytes := bytes.ToLower([]byte(ll.LogMarkerHeaderName))
 
 	var line []byte
 	lineCounter := 0
diff --git a/waflog/types.go b/waflog/types.go
index add8e54..7c845c3 100644
--- a/waflog/types.go
+++ b/waflog/types.go
@@ -1,15 +1,14 @@
 // Package waflog encapsulates getting logs from a WAF to compare with expected results
 package waflog
 
-import "os"
+import (
+	"os"
+)
 
 // FTWLogLines represents the filename to search for logs in a certain timespan
 type FTWLogLines struct {
-	logFile     *os.File
-	FileName    string
-	StartMarker []byte
-	EndMarker   []byte
+	logFile             *os.File
+	LogMarkerHeaderName []byte
+	StartMarker         []byte
+	EndMarker           []byte
 }
-
-// FTWLogOption follows the option pattern for FTWLogLines
-type FTWLogOption func(*FTWLogLines)
diff --git a/waflog/waflog.go b/waflog/waflog.go
index f20580b..f3aca27 100644
--- a/waflog/waflog.go
+++ b/waflog/waflog.go
@@ -9,54 +9,34 @@ import (
 	"github.com/coreruleset/go-ftw/config"
 )
 
-var errNoLogFile = errors.New("no log file supplied")
-
 // NewFTWLogLines is the base struct for reading the log file
-func NewFTWLogLines(opts ...FTWLogOption) (*FTWLogLines, error) {
+func NewFTWLogLines(cfg *config.FTWConfiguration) (*FTWLogLines, error) {
 	ll := &FTWLogLines{
-		logFile:     nil,
-		FileName:    config.FTWConfig.LogFile,
-		StartMarker: nil,
-		EndMarker:   nil,
-	}
-
-	// Loop through each option
-	for _, opt := range opts {
-		// Call the option giving the instantiated
-		// *FTWLogOption as the argument
-		opt(ll)
+		logFile:             nil,
+		LogMarkerHeaderName: bytes.ToLower([]byte(cfg.LogMarkerHeaderName)),
+		StartMarker:         nil,
+		EndMarker:           nil,
 	}
 
-	if err := ll.openLogFile(); err != nil {
+	if err := ll.openLogFile(cfg); err != nil {
 		return nil, fmt.Errorf("cannot open log file: %w", err)
 	}
 
-	if config.FTWConfig.RunMode == config.DefaultRunMode && ll.logFile == nil {
-		return nil, errNoLogFile
+	if cfg.RunMode == config.DefaultRunMode && ll.logFile == nil {
+		return nil, errors.New("no log file supplied")
 	}
 
 	return ll, nil
 }
 
 // WithStartMarker sets the start marker for the log file
-func WithStartMarker(marker []byte) FTWLogOption {
-	return func(ll *FTWLogLines) {
-		ll.StartMarker = bytes.ToLower(marker)
-	}
+func (ll *FTWLogLines) WithStartMarker(marker []byte) {
+	ll.StartMarker = bytes.ToLower(marker)
 }
 
 // WithEndMarker sets the end marker for the log file
-func WithEndMarker(marker []byte) FTWLogOption {
-	return func(ll *FTWLogLines) {
-		ll.EndMarker = bytes.ToLower(marker)
-	}
-}
-
-// WithLogFile sets the log file to read
-func WithLogFile(fileName string) FTWLogOption {
-	return func(ll *FTWLogLines) {
-		ll.FileName = fileName
-	}
+func (ll *FTWLogLines) WithEndMarker(marker []byte) {
+	ll.EndMarker = bytes.ToLower(marker)
 }
 
 // Cleanup closes the log file
@@ -67,12 +47,12 @@ func (ll *FTWLogLines) Cleanup() error {
 	return nil
 }
 
-func (ll *FTWLogLines) openLogFile() error {
+func (ll *FTWLogLines) openLogFile(cfg *config.FTWConfiguration) error {
 	// Using a log file is not required in cloud mode
-	if config.FTWConfig.RunMode == config.DefaultRunMode {
-		if ll.FileName != "" && ll.logFile == nil {
+	if cfg.RunMode == config.DefaultRunMode {
+		if cfg.LogFile != "" && ll.logFile == nil {
 			var err error
-			ll.logFile, err = os.Open(ll.FileName)
+			ll.logFile, err = os.Open(cfg.LogFile)
 			return err
 		}
 	}


Test patch:
diff --git a/check/base_test.go b/check/base_test.go
index dc34681..9ee2017 100644
--- a/check/base_test.go
+++ b/check/base_test.go
@@ -26,12 +26,12 @@ mode: "cloud"
 `
 
 func TestNewCheck(t *testing.T) {
-	err := config.NewConfigFromString(yamlNginxConfig)
+	cfg, err := config.NewConfigFromString(yamlNginxConfig)
 	assert.NoError(t, err)
 
-	c := NewCheck(config.FTWConfig)
+	c := NewCheck(cfg)
 
-	for _, text := range c.overrides.Ignore {
+	for _, text := range c.cfg.TestOverride.Ignore {
 		assert.Equal(t, text, "Ignore Me", "Well, didn't match Ignore Me")
 	}
 
@@ -52,10 +52,10 @@ func TestNewCheck(t *testing.T) {
 }
 
 func TestForced(t *testing.T) {
-	err := config.NewConfigFromString(yamlNginxConfig)
+	cfg, err := config.NewConfigFromString(yamlNginxConfig)
 	assert.NoError(t, err)
 
-	c := NewCheck(config.FTWConfig)
+	c := NewCheck(cfg)
 
 	assert.True(t, c.ForcedIgnore("942200-1"), "Can't find ignored value")
 
@@ -65,10 +65,10 @@ func TestForced(t *testing.T) {
 }
 
 func TestCloudMode(t *testing.T) {
-	err := config.NewConfigFromString(yamlCloudConfig)
+	cfg, err := config.NewConfigFromString(yamlCloudConfig)
 	assert.NoError(t, err)
 
-	c := NewCheck(config.FTWConfig)
+	c := NewCheck(cfg)
 
 	assert.True(t, c.CloudMode(), "couldn't detect cloud mode")
 
diff --git a/check/error_test.go b/check/error_test.go
index eb45a5e..15c7406 100644
--- a/check/error_test.go
+++ b/check/error_test.go
@@ -26,10 +26,10 @@ var expectedFailTests = []struct {
 }
 
 func TestAssertResponseErrorOK(t *testing.T) {
-	err := config.NewConfigFromString(yamlApacheConfig)
+	cfg, err := config.NewConfigFromString(yamlApacheConfig)
 	assert.NoError(t, err)
 
-	c := NewCheck(config.FTWConfig)
+	c := NewCheck(cfg)
 	for _, e := range expectedOKTests {
 		c.SetExpectError(e.expected)
 		assert.Equal(t, e.expected, c.AssertExpectError(e.err))
@@ -37,10 +37,10 @@ func TestAssertResponseErrorOK(t *testing.T) {
 }
 
 func TestAssertResponseFail(t *testing.T) {
-	err := config.NewConfigFromString(yamlApacheConfig)
+	cfg, err := config.NewConfigFromString(yamlApacheConfig)
 	assert.NoError(t, err)
 
-	c := NewCheck(config.FTWConfig)
+	c := NewCheck(cfg)
 
 	for _, e := range expectedFailTests {
 		c.SetExpectError(e.expected)
diff --git a/check/logs_test.go b/check/logs_test.go
index 06204fc..746d660 100644
--- a/check/logs_test.go
+++ b/check/logs_test.go
@@ -17,14 +17,14 @@ var logText = `[Tue Jan 05 02:21:09.637165 2021] [:error] [pid 76:tid 1396834345
 `
 
 func TestAssertLogContainsOK(t *testing.T) {
-	err := config.NewConfigFromString(yamlApacheConfig)
+	cfg, err := config.NewConfigFromString(yamlApacheConfig)
 	assert.NoError(t, err)
 
 	logName, _ := utils.CreateTempFileWithContent(logText, "test-*.log")
 	defer os.Remove(logName)
-	config.FTWConfig.LogFile = logName
+	cfg.WithLogfile(logName)
 
-	c := NewCheck(config.FTWConfig)
+	c := NewCheck(cfg)
 
 	c.SetLogContains(`id "920300"`)
 	assert.True(t, c.AssertLogContains(), "did not find expected content 'id \"920300\"'")
diff --git a/check/response_test.go b/check/response_test.go
index 81418ab..8376350 100644
--- a/check/response_test.go
+++ b/check/response_test.go
@@ -23,10 +23,10 @@ var expectedResponseFailTests = []struct {
 }
 
 func TestAssertResponseTextErrorOK(t *testing.T) {
-	err := config.NewConfigFromString(yamlApacheConfig)
+	cfg, err := config.NewConfigFromString(yamlApacheConfig)
 	assert.NoError(t, err)
 
-	c := NewCheck(config.FTWConfig)
+	c := NewCheck(cfg)
 	for _, e := range expectedResponseOKTests {
 		c.SetExpectResponse(e.expected)
 		assert.Truef(t, c.AssertResponseContains(e.response), "unexpected response: %v", e.response)
@@ -34,10 +34,10 @@ func TestAssertResponseTextErrorOK(t *testing.T) {
 }
 
 func TestAssertResponseTextFailOK(t *testing.T) {
-	err := config.NewConfigFromString(yamlApacheConfig)
+	cfg, err := config.NewConfigFromString(yamlApacheConfig)
 	assert.NoError(t, err)
 
-	c := NewCheck(config.FTWConfig)
+	c := NewCheck(cfg)
 	for _, e := range expectedResponseFailTests {
 		c.SetExpectResponse(e.expected)
 		assert.Falsef(t, c.AssertResponseContains(e.response), "response shouldn't contain text %v", e.response)
diff --git a/check/status_test.go b/check/status_test.go
index 4c488e0..95fbb06 100644
--- a/check/status_test.go
+++ b/check/status_test.go
@@ -26,10 +26,10 @@ var statusFailTests = []struct {
 }
 
 func TestStatusOK(t *testing.T) {
-	err := config.NewConfigFromString(yamlApacheConfig)
+	cfg, err := config.NewConfigFromString(yamlApacheConfig)
 	assert.NoError(t, err)
 
-	c := NewCheck(config.FTWConfig)
+	c := NewCheck(cfg)
 
 	for _, expected := range statusOKTests {
 		c.SetExpectStatus(expected.expectedStatus)
@@ -38,10 +38,10 @@ func TestStatusOK(t *testing.T) {
 }
 
 func TestStatusFail(t *testing.T) {
-	err := config.NewConfigFromString(yamlApacheConfig)
+	cfg, err := config.NewConfigFromString(yamlApacheConfig)
 	assert.NoError(t, err)
 
-	c := NewCheck(config.FTWConfig)
+	c := NewCheck(cfg)
 
 	for _, expected := range statusFailTests {
 		c.SetExpectStatus(expected.expectedStatus)
diff --git a/config/config_test.go b/config/config_test.go
index 2b6b07c..0d2f93a 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -5,6 +5,8 @@ import (
 	"regexp"
 	"testing"
 
+	"github.com/coreruleset/go-ftw/test"
+
 	"github.com/stretchr/testify/assert"
 
 	"github.com/coreruleset/go-ftw/utils"
@@ -34,27 +36,49 @@ var jsonConfig = `
 {"test": "type"}
 `
 
+func TestNewConfig(t *testing.T) {
+	overrides := FTWTestOverride{
+		Input:     test.Input{},
+		Ignore:    nil,
+		ForcePass: nil,
+		ForceFail: nil,
+	}
+	cfg := NewConfig("mylogfile.log", overrides, "X-Test-Me", "cloud")
+	assert.Equal(t, "mylogfile.log", cfg.LogFile)
+	assert.Equal(t, "X-Test-Me", cfg.LogMarkerHeaderName)
+	assert.Equal(t, CloudRunMode, cfg.RunMode)
+}
+
+func TestNewDefaultConfig(t *testing.T) {
+	cfg := NewDefaultConfig()
+	assert.Equal(t, DefaultLogMarkerHeaderName, cfg.LogMarkerHeaderName)
+	assert.Equal(t, DefaultRunMode, cfg.RunMode)
+	assert.Equal(t, "", cfg.LogFile)
+}
+
 func TestNewConfigBadFileConfig(t *testing.T) {
 	filename, _ := utils.CreateTempFileWithContent(jsonConfig, "test-*.yaml")
 	defer os.Remove(filename)
-	err := NewConfigFromFile(filename)
+	cfg, err := NewConfigFromFile(filename)
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 }
 
 func TestNewConfigConfig(t *testing.T) {
 	filename, _ := utils.CreateTempFileWithContent(yamlConfig, "test-*.yaml")
 
-	err := NewConfigFromFile(filename)
+	cfg, err := NewConfigFromFile(filename)
 
 	assert.NoError(t, err)
-	assert.NotEmpty(t, FTWConfig.TestOverride.Input, "Ignore list must not be empty")
+	assert.NotNil(t, cfg)
+	assert.NotEmpty(t, cfg.TestOverride.Input, "Ignore list must not be empty")
 
-	for id, text := range FTWConfig.TestOverride.Ignore {
+	for id, text := range cfg.TestOverride.Ignore {
 		assert.Contains(t, (*regexp.Regexp)(id).String(), "920400-1$", "Looks like we could not find item to ignore")
 		assert.Equal(t, "This test must be ignored", text, "Text doesn't match")
 	}
 
-	overrides := FTWConfig.TestOverride.Input
+	overrides := cfg.TestOverride.Input
 	assert.NotNil(t, overrides.DestAddr, "Looks like we are not overriding destination address")
 	assert.Equal(t, "httpbin.org", *overrides.DestAddr, "Looks like we are not overriding destination address")
 }
@@ -62,9 +86,10 @@ func TestNewConfigConfig(t *testing.T) {
 func TestNewConfigBadConfig(t *testing.T) {
 	filename, _ := utils.CreateTempFileWithContent(yamlBadConfig, "test-*.yaml")
 	defer os.Remove(filename)
-	_ = NewConfigFromFile(filename)
+	cfg, err := NewConfigFromFile(filename)
 
-	assert.NotNil(t, FTWConfig)
+	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 }
 
 func TestNewConfigDefaultConfig(t *testing.T) {
@@ -75,39 +100,42 @@ func TestNewConfigDefaultConfig(t *testing.T) {
 		os.Remove(fileName)
 	})
 
-	_ = NewConfigFromFile("")
-
-	assert.NotNil(t, FTWConfig)
+	cfg, err := NewConfigFromFile("")
+	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 }
 
 func TestNewConfigFromString(t *testing.T) {
-	err := NewConfigFromString(yamlConfig)
+	cfg, err := NewConfigFromString(yamlConfig)
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 }
 
 func TestNewEnvConfigFromString(t *testing.T) {
-	err := NewConfigFromString(yamlConfig)
+	cfg, err := NewConfigFromString(yamlConfig)
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 }
 
 func TestNewConfigFromEnv(t *testing.T) {
 	// Set some environment so it gets merged with conf
 	os.Setenv("FTW_LOGFILE", "koanf")
 
-	err := NewConfigFromEnv()
+	cfg, err := NewConfigFromEnv()
 	assert.NoError(t, err)
-
-	assert.Equal(t, "koanf", FTWConfig.LogFile)
+	assert.NotNil(t, cfg)
+	assert.Equal(t, "koanf", cfg.LogFile)
 }
 
 func TestNewConfigFromEnvHasDefaults(t *testing.T) {
-	err := NewConfigFromEnv()
+	cfg, err := NewConfigFromEnv()
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 
-	assert.Equalf(t, DefaultRunMode, FTWConfig.RunMode,
-		"unexpected default value '%s' for run mode", FTWConfig.RunMode)
-	assert.Equalf(t, DefaultLogMarkerHeaderName, FTWConfig.LogMarkerHeaderName,
-		"unexpected default value '%s' for logmarkerheadername", FTWConfig.LogMarkerHeaderName)
+	assert.Equalf(t, DefaultRunMode, cfg.RunMode,
+		"unexpected default value '%s' for run mode", cfg.RunMode)
+	assert.Equalf(t, DefaultLogMarkerHeaderName, cfg.LogMarkerHeaderName,
+		"unexpected default value '%s' for logmarkerheadername", cfg.LogMarkerHeaderName)
 
 }
 
@@ -115,32 +143,50 @@ func TestNewConfigFromFileHasDefaults(t *testing.T) {
 	filename, _ := utils.CreateTempFileWithContent(yamlConfig, "test-*.yaml")
 	defer os.Remove(filename)
 
-	err := NewConfigFromFile(filename)
+	cfg, err := NewConfigFromFile(filename)
 	assert.NoError(t, err)
-
-	assert.Equalf(t, DefaultRunMode, FTWConfig.RunMode,
-		"unexpected default value '%s' for run mode", FTWConfig.RunMode)
-	assert.Equalf(t, DefaultLogMarkerHeaderName, FTWConfig.LogMarkerHeaderName,
-		"unexpected default value '%s' for logmarkerheadername", FTWConfig.LogMarkerHeaderName)
+	assert.NotNil(t, cfg)
+	assert.Equalf(t, DefaultRunMode, cfg.RunMode,
+		"unexpected default value '%s' for run mode", cfg.RunMode)
+	assert.Equalf(t, DefaultLogMarkerHeaderName, cfg.LogMarkerHeaderName,
+		"unexpected default value '%s' for logmarkerheadername", cfg.LogMarkerHeaderName)
 }
 
 func TestNewConfigFromStringHasDefaults(t *testing.T) {
-	err := NewConfigFromString("")
+	cfg, err := NewConfigFromString("")
 	assert.NoError(t, err)
-
-	assert.Equalf(t, DefaultRunMode, FTWConfig.RunMode,
-		"unexpected default value '%s' for run mode", FTWConfig.RunMode)
-	assert.Equalf(t, DefaultLogMarkerHeaderName, FTWConfig.LogMarkerHeaderName,
-		"unexpected default value '%s' for logmarkerheadername", FTWConfig.LogMarkerHeaderName)
+	assert.NotNil(t, cfg)
+	assert.Equalf(t, DefaultRunMode, cfg.RunMode,
+		"unexpected default value '%s' for run mode", cfg.RunMode)
+	assert.Equalf(t, DefaultLogMarkerHeaderName, cfg.LogMarkerHeaderName,
+		"unexpected default value '%s' for logmarkerheadername", cfg.LogMarkerHeaderName)
 }
 
 func TestNewConfigFromFileRunMode(t *testing.T) {
 	filename, _ := utils.CreateTempFileWithContent(yamlCloudConfig, "test-*.yaml")
 	defer os.Remove(filename)
 
-	err := NewConfigFromFile(filename)
+	cfg, err := NewConfigFromFile(filename)
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
+	assert.Equalf(t, CloudRunMode, cfg.RunMode,
+		"unexpected value '%s' for run mode, expected '%s;", cfg.RunMode, CloudRunMode)
+}
 
-	assert.Equalf(t, CloudRunMode, FTWConfig.RunMode,
-		"unexpected value '%s' for run mode, expected '%s;", FTWConfig.RunMode, CloudRunMode)
+func TestNewDefaultConfigWithParams(t *testing.T) {
+	cfg := NewDefaultConfig()
+	cfg.WithLogfile("mylogfile.log")
+	assert.Equal(t, "mylogfile.log", cfg.LogFile)
+	overrides := FTWTestOverride{
+		Input:     test.Input{},
+		Ignore:    nil,
+		ForcePass: nil,
+		ForceFail: nil,
+	}
+	cfg.WithOverrides(overrides)
+	assert.Equal(t, overrides, cfg.TestOverride)
+	cfg.WithLogMarkerHeaderName("NEW-MARKER-TEST")
+	assert.Equal(t, "NEW-MARKER-TEST", cfg.LogMarkerHeaderName)
+	cfg.WithRunMode(CloudRunMode)
+	assert.Equal(t, CloudRunMode, cfg.RunMode)
 }
diff --git a/runner/run_test.go b/runner/run_test.go
index b6bdad8..46ad4c1 100644
--- a/runner/run_test.go
+++ b/runner/run_test.go
@@ -2,13 +2,14 @@ package runner
 
 import (
 	"fmt"
-	"github.com/coreruleset/go-ftw/output"
 	"net/http"
 	"net/http/httptest"
 	"os"
 	"regexp"
 	"testing"
 
+	"github.com/coreruleset/go-ftw/output"
+
 	"github.com/stretchr/testify/assert"
 
 	"github.com/rs/zerolog/log"
@@ -292,14 +293,14 @@ tests:
 `
 
 // Error checking omitted for brevity
-func newTestServer(t *testing.T, logLines string) (destination *ftwhttp.Destination, logFilePath string) {
-	logFilePath = setUpLogFileForTestServer(t)
+func newTestServer(t *testing.T, cfg *config.FTWConfiguration, logLines string) (destination *ftwhttp.Destination, logFilePath string) {
+	logFilePath = setUpLogFileForTestServer(t, cfg)
 
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.WriteHeader(http.StatusOK)
 		_, _ = w.Write([]byte("Hello, client"))
 
-		writeTestServerLog(t, logLines, logFilePath, r)
+		writeTestServerLog(t, cfg, logLines, logFilePath, r)
 	}))
 
 	// close server after test
@@ -330,10 +331,10 @@ func newTestServerForCloudTest(t *testing.T, responseStatus int) (server *httpte
 	return server, dest
 }
 
-func setUpLogFileForTestServer(t *testing.T) (logFilePath string) {
+func setUpLogFileForTestServer(t *testing.T, cfg *config.FTWConfiguration) (logFilePath string) {
 	// log to the configured file
-	if config.FTWConfig != nil && config.FTWConfig.RunMode == config.DefaultRunMode {
-		logFilePath = config.FTWConfig.LogFile
+	if cfg.RunMode == config.DefaultRunMode {
+		logFilePath = cfg.LogFile
 	}
 	// if no file has been configured, create one and handle cleanup
 	if logFilePath == "" {
@@ -348,12 +349,12 @@ func setUpLogFileForTestServer(t *testing.T) (logFilePath string) {
 	return logFilePath
 }
 
-func writeTestServerLog(t *testing.T, logLines string, logFilePath string, r *http.Request) {
+func writeTestServerLog(t *testing.T, cfg *config.FTWConfiguration, logLines string, logFilePath string, r *http.Request) {
 	// write supplied log lines, emulating the output of the rule engine
 	logMessage := logLines
 	// if the request has the special test header, log the request instead
 	// this emulates the log marker rule
-	if r.Header.Get(config.FTWConfig.LogMarkerHeaderName) != "" {
+	if r.Header.Get(cfg.LogMarkerHeaderName) != "" {
 		logMessage = fmt.Sprintf("request line: %s %s %s, headers: %s\n", r.Method, r.RequestURI, r.Proto, r.Header)
 	}
 	file, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
@@ -390,11 +391,11 @@ func replaceDestinationInTest(ftwTest *test.FTWTest, d ftwhttp.Destination) {
 	}
 }
 
-func replaceDestinationInConfiguration(dest ftwhttp.Destination) {
+func replaceDestinationInConfiguration(override *config.FTWTestOverride, dest ftwhttp.Destination) {
 	replaceableAddress := "TEST_ADDR"
 	replaceablePort := -1
 
-	input := &config.FTWConfig.TestOverride.Input
+	input := &override.Input
 	if input.DestAddr != nil && *input.DestAddr == replaceableAddress {
 		input.DestAddr = &dest.DestAddr
 	}
@@ -404,23 +405,21 @@ func replaceDestinationInConfiguration(dest ftwhttp.Destination) {
 }
 
 func TestRun(t *testing.T) {
-	t.Cleanup(config.Reset)
-
-	err := config.NewConfigFromString(yamlConfig)
+	cfg, err := config.NewConfigFromString(yamlConfig)
 	assert.NoError(t, err)
 
 	out := output.NewOutput("normal", os.Stdout)
 
 	// setup test webserver (not a waf)
-	dest, logFilePath := newTestServer(t, logText)
-	config.FTWConfig.LogFile = logFilePath
+	dest, logFilePath := newTestServer(t, cfg, logText)
+	cfg.WithLogfile(logFilePath)
 	ftwTest, err := test.GetTestFromYaml([]byte(yamlTest))
 	assert.NoError(t, err)
 
 	replaceDestinationInTest(&ftwTest, *dest)
 
 	t.Run("show time and execute all", func(t *testing.T) {
-		res, err := Run([]test.FTWTest{ftwTest}, Config{
+		res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
 			ShowTime: true,
 			Output:   output.Quiet,
 		}, out)
@@ -429,7 +428,7 @@ func TestRun(t *testing.T) {
 	})
 
 	t.Run("be verbose and execute all", func(t *testing.T) {
-		res, err := Run([]test.FTWTest{ftwTest}, Config{
+		res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
 			Include:  regexp.MustCompile("0*"),
 			ShowTime: true,
 		}, out)
@@ -438,7 +437,7 @@ func TestRun(t *testing.T) {
 	})
 
 	t.Run("don't show time and execute all", func(t *testing.T) {
-		res, err := Run([]test.FTWTest{ftwTest}, Config{
+		res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
 			Include: regexp.MustCompile("0*"),
 		}, out)
 		assert.NoError(t, err)
@@ -446,7 +445,7 @@ func TestRun(t *testing.T) {
 	})
 
 	t.Run("execute only test 008 but exclude all", func(t *testing.T) {
-		res, err := Run([]test.FTWTest{ftwTest}, Config{
+		res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
 			Include: regexp.MustCompile("008"),
 			Exclude: regexp.MustCompile("0*"),
 		}, out)
@@ -455,7 +454,7 @@ func TestRun(t *testing.T) {
 	})
 
 	t.Run("exclude test 010", func(t *testing.T) {
-		res, err := Run([]test.FTWTest{ftwTest}, Config{
+		res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
 			Exclude: regexp.MustCompile("010"),
 		}, out)
 		assert.NoError(t, err)
@@ -463,7 +462,7 @@ func TestRun(t *testing.T) {
 	})
 
 	t.Run("test exceptions 1", func(t *testing.T) {
-		res, err := Run([]test.FTWTest{ftwTest}, Config{
+		res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
 			Include: regexp.MustCompile("1*"),
 			Exclude: regexp.MustCompile("0*"),
 			Output:  output.Quiet,
@@ -474,18 +473,16 @@ func TestRun(t *testing.T) {
 }
 
 func TestOverrideRun(t *testing.T) {
-	t.Cleanup(config.Reset)
-
 	// setup test webserver (not a waf)
-	err := config.NewConfigFromString(yamlConfigOverride)
+	cfg, err := config.NewConfigFromString(yamlConfigOverride)
 	assert.NoError(t, err)
 
 	out := output.NewOutput("normal", os.Stdout)
 
-	dest, logFilePath := newTestServer(t, logText)
+	dest, logFilePath := newTestServer(t, cfg, logText)
 
-	replaceDestinationInConfiguration(*dest)
-	config.FTWConfig.LogFile = logFilePath
+	replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
+	cfg.WithLogfile(logFilePath)
 
 	// replace host and port with values that can be overridden by config
 	fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234")
@@ -498,7 +495,7 @@ func TestOverrideRun(t *testing.T) {
 
 	replaceDestinationInTest(&ftwTest, *fakeDestination)
 
-	res, err := Run([]test.FTWTest{ftwTest}, Config{
+	res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{
 		Output: output.Quiet,
 	}, out)
 	assert.NoError(t, err)
@@ -506,17 +503,15 @@ func TestOverrideRun(t *testing.T) {
 }
 
 func TestBrokenOverrideRun(t *testing.T) {
-	t.Cleanup(config.Reset)
-
-	err := config.NewConfigFromString(yamlBrokenConfigOverride)
+	cfg, err := config.NewConfigFromString(yamlBrokenConfigOverride)
 	assert.NoError(t, err)
 
 	out := output.NewOutput("normal", os.Stdout)
 
-	dest, logFilePath := newTestServer(t, logText)
+	dest, logFilePath := newTestServer(t, cfg, logText)
+	cfg.WithLogfile(logFilePath)
 
-	replaceDestinationInConfiguration(*dest)
-	config.FTWConfig.LogFile = logFilePath
+	replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
 
 	// replace host and port with values that can be overridden by config
 	fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234")
@@ -530,25 +525,23 @@ func TestBrokenOverrideRun(t *testing.T) {
 	replaceDestinationInTest(&ftwTest, *fakeDestination)
 
 	// the test should succeed, despite the unknown override property
-	res, err := Run([]test.FTWTest{ftwTest}, Config{}, out)
+	res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{}, out)
 	assert.NoError(t, err)
 	assert.LessOrEqual(t, 0, res.Stats.TotalFailed(), "Oops, test run failed!")
 }
 
 func TestBrokenPortOverrideRun(t *testing.T) {
-	t.Cleanup(config.Reset)
-
+	defaultConfig := config.NewDefaultConfig()
 	// TestServer initialized first to retrieve the correct port number
-	dest, logFilePath := newTestServer(t, logText)
-
+	dest, logFilePath := newTestServer(t, defaultConfig, logText)
 	// replace destination port inside the yaml with the retrieved one
-	err := config.NewConfigFromString(fmt.Sprintf(yamlConfigPortOverride, dest.Port))
+	cfg, err := config.NewConfigFromString(fmt.Sprintf(yamlConfigPortOverride, dest.Port))
 	assert.NoError(t, err)
 
 	out := output.NewOutput("normal", os.Stdout)
 
-	replaceDestinationInConfiguration(*dest)
-	config.FTWConfig.LogFile = logFilePath
+	replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
+	cfg.WithLogfile(logFilePath)
 
 	// replace host and port with values that can be overridden by config
 	fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234")
@@ -562,15 +555,13 @@ func TestBrokenPortOverrideRun(t *testing.T) {
 	replaceDestinationInTest(&ftwTest, *fakeDestination)
 
 	// the test should succeed, despite the unknown override property
-	res, err := Run([]test.FTWTest{ftwTest}, Config{}, out)
+	res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{}, out)
 	assert.NoError(t, err)
 	assert.LessOrEqual(t, 0, res.Stats.TotalFailed(), "Oops, test run failed!")
 }
 
 func TestDisabledRun(t *testing.T) {
-	t.Cleanup(config.Reset)
-
-	err := config.NewConfigFromString(yamlCloudConfig)
+	cfg, err := config.NewConfigFromString(yamlCloudConfig)
 	assert.NoError(t, err)
 	out := output.NewOutput("normal", os.Stdout)
 
@@ -583,21 +574,19 @@ func TestDisabledRun(t *testing.T) {
 	assert.NoError(t, err)
 	replaceDestinationInTest(&ftwTest, *fakeDestination)
 
-	res, err := Run([]test.FTWTest{ftwTest}, Config{}, out)
+	res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{}, out)
 	assert.NoError(t, err)
 	assert.LessOrEqual(t, 0, res.Stats.TotalFailed(), "Oops, test run failed!")
 }
 
 func TestLogsRun(t *testing.T) {
-	t.Cleanup(config.Reset)
-
+	cfg, err := config.NewConfigFromString(yamlConfig)
+	assert.NoError(t, err)
 	// setup test webserver (not a waf)
-	dest, logFilePath := newTestServer(t, logText)
+	dest, logFilePath := newTestServer(t, cfg, logText)
 
-	err := config.NewConfigFromString(yamlConfig)
-	assert.NoError(t, err)
-	replaceDestinationInConfiguration(*dest)
-	config.FTWConfig.LogFile = logFilePath
+	replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
+	cfg.WithLogfile(logFilePath)
 
 	out := output.NewOutput("normal", os.Stdout)
 
@@ -605,15 +594,13 @@ func TestLogsRun(t *testing.T) {
 	assert.NoError(t, err)
 	replaceDestinationInTest(&ftwTest, *dest)
 
-	res, err := Run([]test.FTWTest{ftwTest}, Config{}, out)
+	res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{}, out)
 	assert.NoError(t, err)
 	assert.LessOrEqual(t, 0, res.Stats.TotalFailed(), "Oops, test run failed!")
 }
 
 func TestCloudRun(t *testing.T) {
-	t.Cleanup(config.Reset)
-
-	err := config.NewConfigFromString(yamlCloudConfig)
+	cfg, err := config.NewConfigFromString(yamlCloudConfig)
 	assert.NoError(t, err)
 	out := output.NewOutput("normal", os.Stdout)
 	stats := NewRunStats()
@@ -632,7 +619,7 @@ func TestCloudRun(t *testing.T) {
 				testCase := &ftwTest.Tests[testCaseIndex]
 				stage := &testCase.Stages[stageIndex].Stage
 
-				ftwCheck := check.NewCheck(config.FTWConfig)
+				ftwCheck := check.NewCheck(cfg)
 
 				// this mirrors check.SetCloudMode()
 				responseStatus := 200
@@ -643,13 +630,14 @@ func TestCloudRun(t *testing.T) {
 				}
 				server, dest := newTestServerForCloudTest(t, responseStatus)
 
-				replaceDestinationInConfiguration(*dest)
+				replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
 
 				replaceDestinationInTest(&ftwTest, *dest)
 				assert.NoError(t, err)
 				client, err := ftwhttp.NewClient(ftwhttp.NewClientConfig())
 				assert.NoError(t, err)
 				runContext := TestRunContext{
+					Config:   cfg,
 					Include:  nil,
 					Exclude:  nil,
 					ShowTime: false,
@@ -670,34 +658,30 @@ func TestCloudRun(t *testing.T) {
 }
 
 func TestFailedTestsRun(t *testing.T) {
-	t.Cleanup(config.Reset)
-
-	err := config.NewConfigFromString(yamlConfig)
+	cfg, err := config.NewConfigFromString(yamlConfig)
 	assert.NoError(t, err)
-	dest, logFilePath := newTestServer(t, logText)
+	dest, logFilePath := newTestServer(t, cfg, logText)
 
 	out := output.NewOutput("normal", os.Stdout)
-	replaceDestinationInConfiguration(*dest)
-	config.FTWConfig.LogFile = logFilePath
+	replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
+	cfg.WithLogfile(logFilePath)
 
 	ftwTest, err := test.GetTestFromYaml([]byte(yamlFailedTest))
 	assert.NoError(t, err)
 	replaceDestinationInTest(&ftwTest, *dest)
 
-	res, err := Run([]test.FTWTest{ftwTest}, Config{}, out)
+	res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{}, out)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, res.Stats.TotalFailed())
 }
 
 func TestApplyInputOverrideSetHostFromDestAddr(t *testing.T) {
-	t.Cleanup(config.Reset)
-
 	originalHost := "original.com"
 	overrideHost := "override.com"
 	testInput := test.Input{
 		DestAddr: &originalHost,
 	}
-	config.FTWConfig = &config.FTWConfiguration{
+	cfg := &config.FTWConfiguration{
 		TestOverride: config.FTWTestOverride{
 			Input: test.Input{
 				DestAddr: &overrideHost,
@@ -705,7 +689,7 @@ func TestApplyInputOverrideSetHostFromDestAddr(t *testing.T) {
 		},
 	}
 
-	err := applyInputOverride(&testInput)
+	err := applyInputOverride(cfg.TestOverride, &testInput)
 	assert.NoError(t, err, "Failed to apply input overrides")
 
 	assert.Equal(t, overrideHost, *testInput.DestAddr, "`dest_addr` should have been overridden")
@@ -718,23 +702,21 @@ func TestApplyInputOverrideSetHostFromDestAddr(t *testing.T) {
 }
 
 func TestIgnoredTestsRun(t *testing.T) {
-	t.Cleanup(config.Reset)
-
-	err := config.NewConfigFromString(yamlConfigIgnoreTests)
-	dest, logFilePath := newTestServer(t, logText)
+	cfg, err := config.NewConfigFromString(yamlConfigIgnoreTests)
+	dest, logFilePath := newTestServer(t, cfg, logText)
 	assert.NoError(t, err)
 
 	out := output.NewOutput("normal", os.Stdout)
 
-	replaceDestinationInConfiguration(*dest)
-	config.FTWConfig.LogFile = logFilePath
+	replaceDestinationInConfiguration(&cfg.TestOverride, *dest)
+	cfg.WithLogfile(logFilePath)
 
 	ftwTest, err := test.GetTestFromYaml([]byte(yamlTest))
 	assert.NoError(t, err)
 
 	replaceDestinationInTest(&ftwTest, *dest)
 
-	res, err := Run([]test.FTWTest{ftwTest}, Config{}, out)
+	res, err := Run(cfg, []test.FTWTest{ftwTest}, RunnerConfig{}, out)
 	assert.NoError(t, err)
 	assert.Equal(t, res.Stats.TotalFailed(), 1, "Oops, test run failed!")
 }
diff --git a/waflog/read_test.go b/waflog/read_test.go
index 18f9d06..d139903 100644
--- a/waflog/read_test.go
+++ b/waflog/read_test.go
@@ -14,8 +14,9 @@ import (
 )
 
 func TestReadCheckLogForMarkerNoMarkerAtEnd(t *testing.T) {
-	err := config.NewConfigFromEnv()
+	cfg, err := config.NewConfigFromEnv()
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 
 	stageID := "dead-beaf-deadbeef-deadbeef-dead"
 	markerLine := "X-cRs-TeSt: " + stageID
@@ -28,19 +29,20 @@ func TestReadCheckLogForMarkerNoMarkerAtEnd(t *testing.T) {
 	filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
 	assert.NoError(t, err)
 
-	config.FTWConfig.LogFile = filename
+	cfg.LogFile = filename
 	t.Cleanup(func() { os.Remove(filename) })
 
-	ll, err := NewFTWLogLines(WithStartMarker([]byte(markerLine)))
+	ll, err := NewFTWLogLines(cfg)
 	assert.NoError(t, err)
-
+	ll.WithStartMarker([]byte(markerLine))
 	marker := ll.CheckLogForMarker(stageID, 100)
 	assert.Equal(t, string(marker), strings.ToLower(markerLine), "unexpectedly found marker")
 }
 
 func TestReadCheckLogForMarkerWithMarkerAtEnd(t *testing.T) {
-	err := config.NewConfigFromEnv()
+	cfg, err := config.NewConfigFromEnv()
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 
 	stageID := "dead-beaf-deadbeef-deadbeef-dead"
 	markerLine := "X-cRs-TeSt: " + stageID
@@ -52,10 +54,11 @@ func TestReadCheckLogForMarkerWithMarkerAtEnd(t *testing.T) {
 	filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
 	assert.NoError(t, err)
 
-	config.FTWConfig.LogFile = filename
+	cfg.LogFile = filename
 	t.Cleanup(func() { os.Remove(filename) })
 
-	ll, err := NewFTWLogLines(WithStartMarker([]byte(markerLine)))
+	ll, err := NewFTWLogLines(cfg)
+	ll.WithStartMarker([]byte(markerLine))
 	assert.NoError(t, err)
 
 	marker := ll.CheckLogForMarker(stageID, 100)
@@ -65,8 +68,9 @@ func TestReadCheckLogForMarkerWithMarkerAtEnd(t *testing.T) {
 }
 
 func TestReadGetMarkedLines(t *testing.T) {
-	err := config.NewConfigFromEnv()
+	cfg, err := config.NewConfigFromEnv()
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 
 	stageID := "dead-beaf-deadbeef-deadbeef-dead"
 	startMarkerLine := "X-cRs-TeSt: " + stageID + " -start"
@@ -79,13 +83,13 @@ func TestReadGetMarkedLines(t *testing.T) {
 	filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
 	assert.NoError(t, err)
 
-	config.FTWConfig.LogFile = filename
+	cfg.LogFile = filename
 	t.Cleanup(func() { os.Remove(filename) })
 
-	ll, err := NewFTWLogLines(
-		WithStartMarker(bytes.ToLower([]byte(startMarkerLine))),
-		WithEndMarker(bytes.ToLower([]byte(endMarkerLine))))
+	ll, err := NewFTWLogLines(cfg)
 	assert.NoError(t, err)
+	ll.WithStartMarker(bytes.ToLower([]byte(startMarkerLine)))
+	ll.WithEndMarker(bytes.ToLower([]byte(endMarkerLine)))
 
 	foundLines := ll.getMarkedLines()
 	// logs are scanned backwards
@@ -102,8 +106,9 @@ func TestReadGetMarkedLines(t *testing.T) {
 }
 
 func TestReadGetMarkedLinesWithTrailingEmptyLines(t *testing.T) {
-	err := config.NewConfigFromEnv()
+	cfg, err := config.NewConfigFromEnv()
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 
 	stageID := "dead-beaf-deadbeef-deadbeef-dead"
 	startMarkerLine := "X-cRs-TeSt: " + stageID + " -start"
@@ -116,13 +121,13 @@ func TestReadGetMarkedLinesWithTrailingEmptyLines(t *testing.T) {
 	filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
 	assert.NoError(t, err)
 
-	config.FTWConfig.LogFile = filename
+	cfg.LogFile = filename
 	t.Cleanup(func() { os.Remove(filename) })
 
-	ll, err := NewFTWLogLines(
-		WithStartMarker(bytes.ToLower([]byte(startMarkerLine))),
-		WithEndMarker(bytes.ToLower([]byte(endMarkerLine))))
+	ll, err := NewFTWLogLines(cfg)
 	assert.NoError(t, err)
+	ll.WithStartMarker(bytes.ToLower([]byte(startMarkerLine)))
+	ll.WithEndMarker(bytes.ToLower([]byte(endMarkerLine)))
 
 	foundLines := ll.getMarkedLines()
 	// logs are scanned backwards
@@ -139,8 +144,9 @@ func TestReadGetMarkedLinesWithTrailingEmptyLines(t *testing.T) {
 }
 
 func TestReadGetMarkedLinesWithPrecedingLines(t *testing.T) {
-	err := config.NewConfigFromEnv()
+	cfg, err := config.NewConfigFromEnv()
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 
 	stageID := "dead-beaf-deadbeef-deadbeef-dead"
 	startMarkerLine := "X-cRs-TeSt: " + stageID + " -start"
@@ -156,13 +162,13 @@ func TestReadGetMarkedLinesWithPrecedingLines(t *testing.T) {
 	filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
 	assert.NoError(t, err)
 
-	config.FTWConfig.LogFile = filename
+	cfg.LogFile = filename
 	t.Cleanup(func() { os.Remove(filename) })
 
-	ll, err := NewFTWLogLines(
-		WithStartMarker(bytes.ToLower([]byte(startMarkerLine))),
-		WithEndMarker(bytes.ToLower([]byte(endMarkerLine))))
+	ll, err := NewFTWLogLines(cfg)
 	assert.NoError(t, err)
+	ll.WithStartMarker(bytes.ToLower([]byte(startMarkerLine)))
+	ll.WithEndMarker(bytes.ToLower([]byte(endMarkerLine)))
 
 	foundLines := ll.getMarkedLines()
 	// logs are scanned backwards
@@ -179,8 +185,9 @@ func TestReadGetMarkedLinesWithPrecedingLines(t *testing.T) {
 }
 
 func TestFTWLogLines_Contains(t *testing.T) {
-	err := config.NewConfigFromEnv()
+	cfg, err := config.NewConfigFromEnv()
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 
 	stageID := "dead-beaf-deadbeef-deadbeef-dead"
 	markerLine := "X-cRs-TeSt: " + stageID
@@ -192,23 +199,23 @@ func TestFTWLogLines_Contains(t *testing.T) {
 	filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
 	assert.NoError(t, err)
 
-	config.FTWConfig.LogFile = filename
+	cfg.LogFile = filename
 	log, err := os.Open(filename)
 	assert.NoError(t, err)
 
 	t.Cleanup(func() { os.Remove(filename) })
 
 	type fields struct {
-		logFile     *os.File
-		FileName    string
-		StartMarker []byte
-		EndMarker   []byte
+		logFile             *os.File
+		LogMarkerHeaderName []byte
+		StartMarker         []byte
+		EndMarker           []byte
 	}
 	f := fields{
-		logFile:     log,
-		FileName:    filename,
-		StartMarker: []byte(markerLine),
-		EndMarker:   []byte(markerLine),
+		logFile:             log,
+		LogMarkerHeaderName: []byte(cfg.LogMarkerHeaderName),
+		StartMarker:         []byte(markerLine),
+		EndMarker:           []byte(markerLine),
 	}
 
 	type args struct {
@@ -240,10 +247,10 @@ func TestFTWLogLines_Contains(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			ll := &FTWLogLines{
-				logFile:     tt.fields.logFile,
-				FileName:    tt.fields.FileName,
-				StartMarker: bytes.ToLower(tt.fields.StartMarker),
-				EndMarker:   bytes.ToLower(tt.fields.EndMarker),
+				logFile:             tt.fields.logFile,
+				LogMarkerHeaderName: bytes.ToLower(tt.fields.LogMarkerHeaderName),
+				StartMarker:         bytes.ToLower(tt.fields.StartMarker),
+				EndMarker:           bytes.ToLower(tt.fields.EndMarker),
 			}
 			got := ll.Contains(tt.args.match)
 			assert.Equalf(t, tt.want, got, "Contains() = %v, want %v", got, tt.want)
@@ -252,8 +259,9 @@ func TestFTWLogLines_Contains(t *testing.T) {
 }
 
 func TestFTWLogLines_ContainsIn404(t *testing.T) {
-	err := config.NewConfigFromEnv()
+	cfg, err := config.NewConfigFromEnv()
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 
 	stageID := "dead-beaf-deadbeef-deadbeef-dead"
 	markerLine := fmt.Sprint(`[2022-11-12 23:08:18.012572] [-:error] 127.0.0.1:36126 Y3AZUo3Gja4gB-tPE9uasgAAAA4 [client 127.0.0.1] ModSecurity: Warning. Unconditional match in SecAction. [file "/apache/conf/httpd.conf_pod_2022-11-12_22:23"] [line "265"] [id "999999"] [msg "`,
@@ -266,23 +274,23 @@ func TestFTWLogLines_ContainsIn404(t *testing.T) {
 	filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
 	assert.NoError(t, err)
 
-	config.FTWConfig.LogFile = filename
+	cfg.LogFile = filename
 	log, err := os.Open(filename)
 	assert.NoError(t, err)
 
 	t.Cleanup(func() { os.Remove(filename) })
 
 	type fields struct {
-		logFile     *os.File
-		FileName    string
-		StartMarker []byte
-		EndMarker   []byte
+		logFile             *os.File
+		LogMarkerHeaderName []byte
+		StartMarker         []byte
+		EndMarker           []byte
 	}
 	f := fields{
-		logFile:     log,
-		FileName:    filename,
-		StartMarker: []byte(markerLine),
-		EndMarker:   []byte(markerLine),
+		logFile:             log,
+		LogMarkerHeaderName: []byte(cfg.LogMarkerHeaderName),
+		StartMarker:         []byte(markerLine),
+		EndMarker:           []byte(markerLine),
 	}
 
 	type args struct {
@@ -306,10 +314,10 @@ func TestFTWLogLines_ContainsIn404(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			ll := &FTWLogLines{
-				logFile:     tt.fields.logFile,
-				FileName:    tt.fields.FileName,
-				StartMarker: bytes.ToLower(tt.fields.StartMarker),
-				EndMarker:   bytes.ToLower(tt.fields.EndMarker),
+				logFile:             tt.fields.logFile,
+				LogMarkerHeaderName: bytes.ToLower(tt.fields.LogMarkerHeaderName),
+				StartMarker:         bytes.ToLower(tt.fields.StartMarker),
+				EndMarker:           bytes.ToLower(tt.fields.EndMarker),
 			}
 			got := ll.Contains(tt.args.match)
 			assert.Equalf(t, tt.want, got, "Contains() = %v, want %v", got, tt.want)
@@ -318,8 +326,9 @@ func TestFTWLogLines_ContainsIn404(t *testing.T) {
 }
 
 func TestFTWLogLines_CheckForLogMarkerIn404(t *testing.T) {
-	err := config.NewConfigFromEnv()
+	cfg, err := config.NewConfigFromEnv()
 	assert.NoError(t, err)
+	assert.NotNil(t, cfg)
 
 	stageID := "dead-beaf-deadbeef-deadbeef-dead"
 	markerLine := fmt.Sprint(`[2022-11-12 23:08:18.012572] [-:error] 127.0.0.1:36126 Y3AZUo3Gja4gB-tPE9uasgAAAA4 [client 127.0.0.1] ModSecurity: Warning. Unconditional match in SecAction. [file "/apache/conf/httpd.conf_pod_2022-11-12_22:23"] [line "265"] [id "999999"] [msg "`,
@@ -332,17 +341,17 @@ func TestFTWLogLines_CheckForLogMarkerIn404(t *testing.T) {
 	filename, err := utils.CreateTempFileWithContent(logLines, "test-errorlog-")
 	assert.NoError(t, err)
 
-	config.FTWConfig.LogFile = filename
+	cfg.LogFile = filename
 	log, err := os.Open(filename)
 	assert.NoError(t, err)
 
 	t.Cleanup(func() { os.Remove(filename) })
 
 	ll := &FTWLogLines{
-		logFile:     log,
-		FileName:    filename,
-		StartMarker: bytes.ToLower([]byte(markerLine)),
-		EndMarker:   bytes.ToLower([]byte(markerLine)),
+		logFile:             log,
+		LogMarkerHeaderName: bytes.ToLower([]byte(cfg.LogMarkerHeaderName)),
+		StartMarker:         bytes.ToLower([]byte(markerLine)),
+		EndMarker:           bytes.ToLower([]byte(markerLine)),
 	}
 	foundMarker := ll.CheckLogForMarker(stageID, 100)
 	assert.Equal(t, strings.ToLower(markerLine), strings.ToLower(string(foundMarker)))
diff --git a/waflog/waflog_test.go b/waflog/waflog_test.go
index 9677d80..3495237 100644
--- a/waflog/waflog_test.go
+++ b/waflog/waflog_test.go
@@ -9,24 +9,16 @@ import (
 )
 
 func TestNewFTWLogLines(t *testing.T) {
-	err := config.NewConfigFromEnv()
-	assert.NoError(t, err)
+	cfg := config.NewDefaultConfig()
+	assert.NotNil(t, cfg)
 
 	// Don't call NewFTWLogLines to avoid opening the file.
 	ll := &FTWLogLines{}
-	// Loop through each option
-	for _, opt := range []FTWLogOption{
-		WithStartMarker([]byte("#")),
-		WithEndMarker([]byte("#")),
-		WithLogFile("test"),
-	} {
-		// Call the option giving the instantiated
-		// *House as the argument
-		opt(ll)
-	}
+	ll.WithStartMarker([]byte("#"))
+	ll.WithEndMarker([]byte("#"))
+
 	assert.NotNil(t, ll.StartMarker, "Failed! StartMarker must be set")
 	assert.NotNil(t, ll.EndMarker, "Failed! EndMarker must be set")
-	assert.Equal(t, "test", ll.FileName, "Failed! FileName must be set")
-	err = ll.Cleanup()
+	err := ll.Cleanup()
 	assert.NoError(t, err)
 }