Skip to content

Commit

Permalink
Merge pull request #63 from theseion/set-host-from-dest_addr-override
Browse files Browse the repository at this point in the history
feat: set "Host" header from dest_addr override
  • Loading branch information
fzipi authored Aug 18, 2022
2 parents fcdc808 + 6ef8c05 commit f5f64a1
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 63 deletions.
11 changes: 8 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
// or uses `.ftw.yaml` as default file
func NewConfigFromFile(cfgFile string) error {
// kaonf merges by default but we never want to merge in this case
FTWConfig = nil
Reset()

// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf.New(".")
Expand Down Expand Up @@ -47,7 +47,7 @@ func NewConfigFromFile(cfgFile string) error {
// NewConfigFromEnv reads configuration information from environment variables that start with `FTW_`
func NewConfigFromEnv() error {
// kaonf merges by default but we never want to merge in this case
FTWConfig = nil
Reset()

var err error
var k = koanf.New(".")
Expand All @@ -70,7 +70,7 @@ func NewConfigFromEnv() error {
// NewConfigFromString initializes the configuration from a yaml formatted string. Useful for testing.
func NewConfigFromString(conf string) error {
// kaonf merges by default but we never want to merge in this case
FTWConfig = nil
Reset()

var k = koanf.New(".")
var err error
Expand All @@ -87,6 +87,11 @@ func NewConfigFromString(conf string) error {
return err
}

// Reset configuration to uninitialized state
func Reset() {
FTWConfig = nil
}

func loadDefaults() {
// Note: kaonf has a way to set defaults. However, kaonf's merge behavior
// will overwrite defaults when the associated field is empty in nested
Expand Down
13 changes: 6 additions & 7 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"os"
"reflect"
"strings"
"testing"

Expand Down Expand Up @@ -53,8 +54,8 @@ func TestNewConfigConfig(t *testing.T) {
t.Errorf("Failed! Len must be > 0")
}

if len(FTWConfig.TestOverride.Input) == 0 {
t.Errorf("Failed! Input Len must be > 0")
if reflect.ValueOf(FTWConfig.TestOverride.Input).IsZero() {
t.Errorf("Failed! Input must not be empty")
}

for id, text := range FTWConfig.TestOverride.Ignore {
Expand All @@ -66,12 +67,10 @@ func TestNewConfigConfig(t *testing.T) {
}
}

for setting, value := range FTWConfig.TestOverride.Input {
if setting == "dest_addr" && value != "httpbin.org" {
t.Errorf("Looks like we are not overriding destination!")
}
overrides := FTWConfig.TestOverride.Input
if overrides.DestAddr != nil && *overrides.DestAddr != "httpbin.org" {
t.Errorf("Looks like we are not overriding destination!")
}

}

func TestNewConfigBadConfig(t *testing.T) {
Expand Down
13 changes: 8 additions & 5 deletions config/types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package config

import "github.com/fzipi/go-ftw/test"

// RunMode represents the mode of the test run
type RunMode string

Expand All @@ -24,12 +26,13 @@ type FTWConfiguration struct {
}

// FTWTestOverride holds four lists:
// Global allows you to override global parameters in tests. An example usage is if you want to change the `dest_addr` of all tests to point to an external IP or host.
// Ignore is for tests you want to ignore. It will still execute the test, but ignore the results. You should add a comment on why you ignore the test
// ForcePass is for tests you want to pass unconditionally. Test will be executed, and pass even when the test fails. You should add a comment on why you force pass the test
// ForceFail is for tests you want to fail unconditionally. Test will be executed, and fail even when the test passes. You should add a comment on why you force fail the test
//
// Input allows you to override input parameters in tests. An example usage is if you want to change the `dest_addr` of all tests to point to an external IP or host.
// Ignore is for tests you want to ignore. It will still execute the test, but ignore the results. You should add a comment on why you ignore the test
// ForcePass is for tests you want to pass unconditionally. Test will be executed, and pass even when the test fails. You should add a comment on why you force pass the test
// ForceFail is for tests you want to fail unconditionally. Test will be executed, and fail even when the test passes. You should add a comment on why you force fail the test
type FTWTestOverride struct {
Input map[string]string `koanf:"input"`
Input test.Input `koanf:"input"`
Ignore map[string]string `koanf:"ignore"`
ForcePass map[string]string `koanf:"forcepass"`
ForceFail map[string]string `koanf:"forcefail"`
Expand Down
44 changes: 19 additions & 25 deletions runner/run.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package runner

import (
"errors"
"fmt"
"regexp"
"strconv"
"time"

"github.com/fzipi/go-ftw/check"
Expand Down Expand Up @@ -103,9 +101,9 @@ func RunStage(runContext *TestRunContext, ftwCheck *check.FTWCheck, testCase tes
}

// Do not even run test if result is overridden. Just use the override and display the overridden result.
if overriden := overridenTestResult(ftwCheck, testCase.TestTitle); overriden != Failed {
addResultToStats(overriden, testCase.TestTitle, &runContext.Stats)
displayResult(runContext.Output, overriden, time.Duration(0), time.Duration(0))
if overridden := overriddenTestResult(ftwCheck, testCase.TestTitle); overridden != Failed {
addResultToStats(overridden, testCase.TestTitle, &runContext.Stats)
displayResult(runContext.Output, overridden, time.Duration(0), time.Duration(0))
return
}

Expand Down Expand Up @@ -268,7 +266,7 @@ func displayResult(quiet bool, result TestResult, roundTripTime time.Duration, s
}
}

func overridenTestResult(c *check.FTWCheck, id string) TestResult {
func overriddenTestResult(c *check.FTWCheck, id string) TestResult {
if c.ForcedIgnore(id) {
return Ignored
}
Expand Down Expand Up @@ -358,28 +356,24 @@ func printUnlessQuietMode(quiet bool, format string, a ...interface{}) {

// applyInputOverride will check if config had global overrides and write that into the test.
func applyInputOverride(testRequest *test.Input) error {
var retErr error
overrides := config.FTWConfig.TestOverride.Input
for s, v := range overrides {
value := v
switch s {
case "port":
port, err := strconv.Atoi(value)
if err != nil {
retErr = errors.New("ftw/run: error getting overriden port")
}
testRequest.Port = &port
case "dest_addr":
oDestAddr := &value
testRequest.DestAddr = oDestAddr
case "protocol":
oProtocol := &value
testRequest.Protocol = oProtocol
default:
retErr = fmt.Errorf("ftw/run: override of '%s' not implemented yet", s)
if overrides.Port != nil {
testRequest.Port = overrides.Port
}
if overrides.DestAddr != nil {
testRequest.DestAddr = overrides.DestAddr
if testRequest.Headers == nil {
testRequest.Headers = ftwhttp.Header{}
}
if testRequest.Headers.Get("Host") == "" {
testRequest.Headers.Set("Host", *overrides.DestAddr)
}
}
return retErr
if overrides.Protocol != nil {
testRequest.Protocol = overrides.Protocol
}

return nil
}

func notRunningInCloudMode(c *check.FTWCheck) bool {
Expand Down
79 changes: 68 additions & 11 deletions runner/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import (
"net/http"
"net/http/httptest"
"os"
"strconv"
"testing"

"github.com/fzipi/go-ftw/check"
"github.com/fzipi/go-ftw/config"
"github.com/fzipi/go-ftw/ftwhttp"
"github.com/fzipi/go-ftw/test"

"github.com/rs/zerolog/log"
)

var yamlConfig = `
Expand Down Expand Up @@ -326,7 +327,10 @@ func setUpLogFileForTestServer(t *testing.T) (logFilePath string) {
t.Error(err)
}
logFilePath = file.Name()
t.Cleanup(func() { os.Remove(logFilePath) })
t.Cleanup(func() {
os.Remove(logFilePath)
log.Info().Msgf("Deleting temporary file '%s'", logFilePath)
})
}
return logFilePath
}
Expand Down Expand Up @@ -376,18 +380,21 @@ func replaceDestinationInTest(ftwTest *test.FTWTest, d ftwhttp.Destination) {
}

func replaceDestinationInConfiguration(dest ftwhttp.Destination) {
input := config.FTWConfig.TestOverride.Input
for key, value := range input {
if key == "dest_addr" && value == "TEST_ADDR" {
input[key] = dest.DestAddr
}
if key == "port" && value == "-1" {
input[key] = strconv.FormatInt(int64(dest.Port), 10)
}
replaceableAddress := "TEST_ADDR"
replaceablePort := -1

input := &config.FTWConfig.TestOverride.Input
if input.DestAddr != nil && *input.DestAddr == replaceableAddress {
input.DestAddr = &dest.DestAddr
}
if input.Port != nil && *input.Port == replaceablePort {
input.Port = &dest.Port
}
}

func TestRun(t *testing.T) {
t.Cleanup(config.Reset)

err := config.NewConfigFromString(yamlConfig)
if err != nil {
t.Errorf("Failed!")
Expand Down Expand Up @@ -440,6 +447,8 @@ func TestRun(t *testing.T) {
}

func TestOverrideRun(t *testing.T) {
t.Cleanup(config.Reset)

// setup test webserver (not a waf)
err := config.NewConfigFromString(yamlConfigOverride)
if err != nil {
Expand Down Expand Up @@ -471,6 +480,8 @@ func TestOverrideRun(t *testing.T) {
}

func TestBrokenOverrideRun(t *testing.T) {
t.Cleanup(config.Reset)

err := config.NewConfigFromString(yamlBrokenConfigOverride)
if err != nil {
t.Errorf("Failed!")
Expand Down Expand Up @@ -502,6 +513,7 @@ func TestBrokenOverrideRun(t *testing.T) {
}

func TestBrokenPortOverrideRun(t *testing.T) {
t.Cleanup(config.Reset)

// TestServer initialized first to retrieve the correct port number
dest, logFilePath := newTestServer(t, logText)
Expand Down Expand Up @@ -536,6 +548,8 @@ func TestBrokenPortOverrideRun(t *testing.T) {
}

func TestDisabledRun(t *testing.T) {
t.Cleanup(config.Reset)

err := config.NewConfigFromString(yamlConfig)
if err != nil {
t.Errorf("Failed!")
Expand All @@ -560,6 +574,8 @@ func TestDisabledRun(t *testing.T) {
}

func TestLogsRun(t *testing.T) {
t.Cleanup(config.Reset)

// setup test webserver (not a waf)
dest, logFilePath := newTestServer(t, logText)

Expand All @@ -584,6 +600,8 @@ func TestLogsRun(t *testing.T) {
}

func TestCloudRun(t *testing.T) {
t.Cleanup(config.Reset)

err := config.NewConfigFromString(yamlCloudConfig)
if err != nil {
t.Errorf("Failed!")
Expand All @@ -597,7 +615,7 @@ func TestCloudRun(t *testing.T) {
t.Run("don't show time and execute all", func(t *testing.T) {
for testCaseIndex, testCaseDummy := range ftwTestDummy.Tests {
for stageIndex := range testCaseDummy.Stages {
// Read the tests for every stage so we an replace the destination
// Read the tests for every stage so we can replace the destination
// in each run. The server needs to be configured for each stage
// individually.
ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs))
Expand Down Expand Up @@ -645,6 +663,8 @@ func TestCloudRun(t *testing.T) {
}

func TestFailedTestsRun(t *testing.T) {
t.Cleanup(config.Reset)

err := config.NewConfigFromString(yamlConfig)
dest, logFilePath := newTestServer(t, logText)
if err != nil {
Expand All @@ -665,3 +685,40 @@ func TestFailedTestsRun(t *testing.T) {
}
})
}

func TestApplyInputOverrideSetHostFromDestAddr(t *testing.T) {
t.Cleanup(config.Reset)

originalHost := "original.com"
overrideHost := "override.com"
testInput := test.Input{
DestAddr: &originalHost,
}
config.FTWConfig = &config.FTWConfiguration{
TestOverride: config.FTWTestOverride{
Input: test.Input{
DestAddr: &overrideHost,
},
},
}

err := applyInputOverride(&testInput)
if err != nil {
t.Error("Failed to apply input overrides", err)
}

if *testInput.DestAddr != overrideHost {
t.Error("`dest_addr` should have been overridden")
}
if testInput.Headers == nil {
t.Error("Header map must exist after overriding `dest_addr`")
}

hostHeader := testInput.Headers.Get("Host")
if hostHeader == "" {
t.Error("Host header must be set after overriding `dest_addr`")
}
if hostHeader != overrideHost {
t.Error("Host header must be identical to `dest_addr` after overrding `dest_addr`")
}
}
25 changes: 13 additions & 12 deletions test/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import "github.com/fzipi/go-ftw/ftwhttp"
// Input represents the input request in a stage
// The fields `Version`, `Method` and `URI` we want to explicitly now when they are set to ""
type Input struct {
DestAddr *string `yaml:"dest_addr,omitempty"`
Port *int `yaml:"port,omitempty"`
Protocol *string `yaml:"protocol,omitempty"`
URI *string `yaml:"uri,omitempty"`
Version *string `yaml:"version,omitempty"`
Headers ftwhttp.Header `yaml:"headers,omitempty"`
Method *string `yaml:"method,omitempty"`
Data *string `yaml:"data,omitempty"`
SaveCookie bool `yaml:"save_cookie,omitempty"`
StopMagic bool `yaml:"stop_magic"`
EncodedRequest string `yaml:"encoded_request,omitempty"`
RAWRequest string `yaml:"raw_request,omitempty"`
DestAddr *string `yaml:"dest_addr,omitempty" koanf:"dest_addr,omitempty"`
Port *int `yaml:"port,omitempty" koanf:"port,omitempty"`
Protocol *string `yaml:"protocol,omitempty" koanf:"protocol,omitempty"`
URI *string `yaml:"uri,omitempty" koanf:"uri,omitempty"`
Version *string `yaml:"version,omitempty" koanf:"version,omitempty"`
Headers ftwhttp.Header `yaml:"headers,omitempty" koanf:"headers,omitempty"`
Method *string `yaml:"method,omitempty" koanf:"method,omitempty"`
Data *string `yaml:"data,omitempty" koanf:"data,omitempty"`
SaveCookie bool `yaml:"save_cookie,omitempty" koanf:"save_cookie,omitempty"`
StopMagic bool `yaml:"stop_magic" koanf:"stop_magic,omitempty"`
EncodedRequest string `yaml:"encoded_request,omitempty" koanf:"encoded_request,omitempty"`
RAWRequest string `yaml:"raw_request,omitempty" koanf:"raw_request,omitempty"`
}

// Output is the response expected from the test
Expand All @@ -28,6 +28,7 @@ type Output struct {
ExpectError bool `yaml:"expect_error,omitempty"`
}

// Stage is an individual test stage
type Stage struct {
Input Input `yaml:"input"`
Output Output `yaml:"output"`
Expand Down

0 comments on commit f5f64a1

Please sign in to comment.