Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: set "Host" header from dest_addr override #63

Merged
merged 1 commit into from
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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