diff --git a/config.yaml b/config.yaml index bc06c52..723047e 100644 --- a/config.yaml +++ b/config.yaml @@ -6,19 +6,19 @@ endpoints: options: rules: - pattern: ".*\\.(?i)exe" - target: ./assets/fakeFiles/sample.exe + response: ./assets/fakeFiles/sample.exe - pattern: ".*\\.(?i)(jpg|jpeg)" - target: ./assets/fakeFiles/default.jpg + response: ./assets/fakeFiles/default.jpg - pattern: ".*\\.(?i)png" - target: ./assets/fakeFiles/default.png + response: ./assets/fakeFiles/default.png - pattern: ".*\\.(?i)gif" - target: ./assets/fakeFiles/default.gif + response: ./assets/fakeFiles/default.gif - pattern: ".*\\.(?i)ico" - target: ./assets/fakeFiles/default.ico + response: ./assets/fakeFiles/default.ico - pattern: ".*\\.(?i)txt" - target: ./assets/fakeFiles/default.txt + response: ./assets/fakeFiles/default.txt - pattern: ".*" - target: ./assets/fakeFiles/default.html + response: ./assets/fakeFiles/default.html httpsDowngrade: handler: tls_interceptor listenAddress: 0.0.0.0 @@ -44,15 +44,15 @@ endpoints: listenAddress: 0.0.0.0 port: 53 options: - fallback: - strategy: incremental - args: - startIP: 10.0.0.0 rules: - pattern: ".*\\.google\\.com" response: 1.1.1.1 - pattern: ".*\\.reddit\\.com" response: 2.2.2.2 + fallback: + strategy: incremental + args: + startIP: 10.0.0.0 dnsOverTlsDowngrade: handler: tls_interceptor listenAddress: 0.0.0.0 diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..7585238 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +book diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 0000000..e1367ec --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,10 @@ +[book] +authors = ["Peter Kurfer"] +language = "en" +multilingual = false +src = "src" +title = "INetMock docs" + +[build] +build-dir = "book" +create-missing = true diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 0000000..9e85f47 --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,13 @@ +# Summary + +- [Building & Installation](build_install.md) +- [Configuration](config.md) + - [`config.yaml`](config/yaml-config.md) + - [`http_mock`](config/http_mock.md) + - [`dns_mock`](config/dns_mock.md) + - [`tls_interceptor`](config/tls_interceptor.md) +- [Deployment](deploy.md) +- [API](api.md) + - [Custom handler](dev/custom_handler.md) + - [Plugin command](dev/plugin_command.md) + - [Logging](dev/logging.md) \ No newline at end of file diff --git a/docs/src/api.md b/docs/src/api.md new file mode 100644 index 0000000..5932792 --- /dev/null +++ b/docs/src/api.md @@ -0,0 +1 @@ +# API diff --git a/docs/src/build_install.md b/docs/src/build_install.md new file mode 100644 index 0000000..bf3698b --- /dev/null +++ b/docs/src/build_install.md @@ -0,0 +1,29 @@ +# Installation + +## Building from source + +### Requirements + +* go 1.14 +* make +* gcc + +### Binary + +To get the binary and all plugins just run + +```bash +make +``` + +and you'll get a `inetmock` binary and a `plugins` directory containing all default plugins. + +The default plugins are: + +* `http_mock` +* `dns_mock` +* `tls_interceptor` + +## Docker/Podman + +## Getting a pre-built binary (coming soon) \ No newline at end of file diff --git a/docs/src/config.md b/docs/src/config.md new file mode 100644 index 0000000..4407a19 --- /dev/null +++ b/docs/src/config.md @@ -0,0 +1,19 @@ +# Configuration + +## Plugins & handlers + +_INetMock_ is based on plugins that ship one or more __protocol handlers__. +Examples for protocol handlers are HTTP or DNS but also TLS. + +The application ships with the following handlers: + +* `http_mock` +* `dns_mock` +* `tls_interceptor` + +The configuration of an so called endpoint always specifies which handler should be used, which IP address and port it should listen on and some handler specific `options`. +This way the whole system is very flexible and can be configured for various individual scenarios. + +## Commands + +Beside of __protocol handlers__ a plugin can also ship custom commands e.g. the `tls_interceptor` ships a `generate-ca` command to bootstrap a certificate authority key-pair that can be reused for multiple instances. \ No newline at end of file diff --git a/docs/src/config/dns_mock.md b/docs/src/config/dns_mock.md new file mode 100644 index 0000000..81b3390 --- /dev/null +++ b/docs/src/config/dns_mock.md @@ -0,0 +1,140 @@ +# `dns_mock` + +## Intro + +The `dns_mock` handler expects an array of rules how it should respond to dfferent DNS queries and a fallback strategy. +Currently only queries for __A__ records are supported. +The rules are primarily meant to define some exceptions or well known DNS responses e.g. to return to right Google DNS IP but for everything else it will return dummy IPs. + +The rules for the `dns_mock` handler are equivalent to the `http_mock` rules. +Every rule consists of a `pattern` that specifies a query name e.g. a single host, a wildcard domain, a wildcard top-level domain or even a _"match all"_ rule is possible. +These rules are evaluated in the same order they are defined in the `config.yaml`. + +The fallback strategy is taken into account whenever a query does not match a rule. + +Right now the following fallback strategies are available: + +* _random_ +* _incremental_ + +Just like the handler is configured via the `options` object the fallback strategies are configured via an `args` object. + +### _random_ fallback + +The _random_ fallback strategy is the easier one of the both. +It doesn't take any argument and it just shuffles a random IP address for every request no matter if it was already asked for this IP or not. + +### _incremental_ fallback + +The _incremental_ fallback is little bit more intelligent. +It takes a `startIP` as an argument which defines from which IP address the strategy starts counting up to respond to DNS queries. +Just like the _incremental_ strategy it is _stateless_ and does not store any already given response for later reuse (at least for now). + +## Configuration + +### Matching an explicit host + +The easiest possible pattern is to match a single host: + +```yml +endpoints: + plainDns: + handler: dns_mock + listenAddress: 0.0.0.0 + port: 53 + options: + rules: + - pattern: "github\\.com" + response: 1.1.1.1 +``` + +### Matching a whole domain + +While matching a single host is nice2have it's not very helpful in most cases - except for some edge cases where it might be necesary to specifically return a certain IP address. +But it's also possible to match a whole domain no matter what subdomain or sub-subdomain or whatever is requested like this: + +```yml +endpoints: + plainDns: + handler: dns_mock + listenAddress: 0.0.0.0 + port: 53 + options: + rules: + - pattern: ".*\\.google\\.com" + response: 2.2.2.2 +``` + +### Matching a whole TLD + +In some cases it might also be interesting to distinguish between different requested TLDs. +Therefore it might be interesting to define one IP address to resolve to for every TLD that should be distinguishable. + +```yml +endpoints: + plainDns: + handler: dns_mock + listenAddress: 0.0.0.0 + port: 53 + options: + rules: + - pattern: ".*\\.com" + response: 2.2.2.2 +``` + +### Matching any query + +Last but not least it is obvously also possible to match any query. +This is comparable to a _"static"_ fallback strategy in cases where different IP addresses are not necessary but the network setup should be as easy as possible. + +```yml +endpoints: + plainDns: + handler: dns_mock + listenAddress: 0.0.0.0 + port: 53 + options: + rules: + - pattern: ".*" + response: 10.0.10.1 +``` + +### Fallback strategies + +#### _random_ + +Like previously mentioned the _random_ strategy is easy as it can be. +It just takes a random unsigned integer of 32 bits, converts it to an IP address and returns this address as response. +Therefore no further configuration is necessary for now. + +```yml +endpoints: + plainDns: + handler: dns_mock + listenAddress: 0.0.0.0 + port: 53 + options: + rules: [] + fallback: + strategy: random +``` + +#### _incremental_ + +Also like previously mentioned the _incremental_ fallback strategy is fairly easy to setup. +It just takes a `startIP` as argument which is used to count upwards. +It does __not__ check for an interval or something like this right now so a overflow might occur. + +```yml +endpoints: + plainDns: + handler: dns_mock + listenAddress: 0.0.0.0 + port: 53 + options: + rules: [] + fallback: + strategy: incremental + args: + startIP: 10.0.0.0 +``` \ No newline at end of file diff --git a/docs/src/config/http_mock.md b/docs/src/config/http_mock.md new file mode 100644 index 0000000..7620085 --- /dev/null +++ b/docs/src/config/http_mock.md @@ -0,0 +1,85 @@ +# `http_mock` + +## Intro + +The `http_mock` handler expects an array of rules how it should respond to different request paths. +This allows to e.g. return an image if the request path contains something like _"asdf.jpg"_ but with binary if the request path contains something like _"malicous.exe"_. + +A _"catch all"_ rule could return in any case an HTML page or if nothing is provided the handler returns an HTTP 404 status code. + +The rules are taken into account in the same order than they are defined in the `config.yaml`. + +Every rule consists of a regex `pattern` (__re2__ compatible) and a `response` path to the file it should return. + +In the future more advanced rules might be possible e.g. to match not on the request path but on some header values. + +## Configuration + +### Matching a specific path + +The easiest possible pattern is to match a static request path: + +```yml +endpoints: + plainHttp: + handler: http_mock + listenAddress: 0.0.0.0 + port: 80 + options: + rules: + - pattern: "/static/http/path/sample.exe" + response: ./assets/fakeFiles/sample.exe +``` + +### Matching a file extensions + +While matching a static path might be nice as an example it's not very useful. +Returning a given file for all kinds of of request paths based on the requested file extension is way more handy: + +```yml +endpoints: + plainHttp: + handler: http_mock + listenAddress: 0.0.0.0 + port: 80 + options: + rules: + - pattern: ".*\\.png" + response: ./assets/fakeFiles/default.png +``` + +So this is already way more flexible but we can do even better: + +```yml +endpoints: + plainHttp: + handler: http_mock + listenAddress: 0.0.0.0 + port: 80 + options: + rules: + - pattern: ".*\\.(?i)(jpg|jpeg)" + response: ./assets/fakeFiles/default.jpg +``` + +This way the extension ignores any case and matches both `.jpg` and `.jpeg` (and of course also e.g. `.JpEg` and so on and so forth). + +The default `config.yaml` already ships with some basic rules to handle the most common file extensions. + +### Defining a fallback + +Last but not least a default case might be necessary to get at least any response but a 404. + +This can be achieved with a `.*` pattern that literally matches everything: + +```yml +endpoints: + plainHttp: + handler: http_mock + listenAddress: 0.0.0.0 + port: 80 + options: + rules: + - pattern: ".*" + response: ./assets/fakeFiles/default.html +``` \ No newline at end of file diff --git a/docs/src/config/tls_interceptor.md b/docs/src/config/tls_interceptor.md new file mode 100644 index 0000000..bd836bb --- /dev/null +++ b/docs/src/config/tls_interceptor.md @@ -0,0 +1 @@ +# `tls_interceptor` \ No newline at end of file diff --git a/docs/src/config/yaml-config.md b/docs/src/config/yaml-config.md new file mode 100644 index 0000000..2b2a4d2 --- /dev/null +++ b/docs/src/config/yaml-config.md @@ -0,0 +1,30 @@ + +# `config.yaml` + +## Intro + +The configuration of _INetMock_ is mostly done in the `config.yaml`. +It defines which endpoints should be started with which handler and a few more things. + +Every endpoint has a name that is used for logging and as already mentioned consists of listening IP and port, the handler and its options. + +INetMock comes with _"Batteries included"_ and ships with a basic `config.yaml` that defines a basic set of endpoints for: + +* HTTP +* HTTPS +* DNS +* DNS-over-TLS + +## Sample + +```yml +endpoints: + myHttpEndpoint: + handler: http_mock + listenAddress: 127.0.0.1 + port: 8080 + options: + rules: + - pattern: ".*" + target: ./assets/fakeFiles/default.html +``` \ No newline at end of file diff --git a/docs/src/deploy.md b/docs/src/deploy.md new file mode 100644 index 0000000..d36c65e --- /dev/null +++ b/docs/src/deploy.md @@ -0,0 +1 @@ +# Deployment diff --git a/docs/src/dev/custom_handler.md b/docs/src/dev/custom_handler.md new file mode 100644 index 0000000..d9e4fc5 --- /dev/null +++ b/docs/src/dev/custom_handler.md @@ -0,0 +1 @@ +# Custom handler diff --git a/docs/src/dev/logging.md b/docs/src/dev/logging.md new file mode 100644 index 0000000..b921bbe --- /dev/null +++ b/docs/src/dev/logging.md @@ -0,0 +1 @@ +# Logging diff --git a/docs/src/dev/plugin_command.md b/docs/src/dev/plugin_command.md new file mode 100644 index 0000000..45aa921 --- /dev/null +++ b/docs/src/dev/plugin_command.md @@ -0,0 +1 @@ +# Plugin command diff --git a/pkg/plugins/http_mock/main.go b/pkg/plugins/http_mock/main.go index ced8971..9ef13f9 100644 --- a/pkg/plugins/http_mock/main.go +++ b/pkg/plugins/http_mock/main.go @@ -8,7 +8,6 @@ import ( "go.uber.org/zap" "net/http" "path/filepath" - "regexp" "sync" ) @@ -58,23 +57,13 @@ func (p *httpHandler) startServer() { } func (p *httpHandler) setupRoute(rule targetRule) { - var compiled *regexp.Regexp - var err error - if compiled, err = regexp.Compile(rule.pattern); err != nil { - p.logger.Warn( - "failed to parse route - skipping", - zap.String("route", rule.pattern), - zap.Error(err), - ) - return - } p.logger.Info( "setup routing", - zap.String("route", compiled.String()), - zap.String("target", rule.target), + zap.String("route", rule.Pattern().String()), + zap.String("response", rule.Response()), ) - p.router.Handler(compiled, createHandlerForTarget(p.logger, rule.target)) + p.router.Handler(rule.Pattern(), createHandlerForTarget(p.logger, rule.response)) } func createHandlerForTarget(logger *zap.Logger, targetPath string) http.Handler { @@ -91,7 +80,7 @@ func createHandlerForTarget(logger *zap.Logger, targetPath string) http.Handler zap.String("method", request.Method), zap.String("protocol", request.Proto), zap.String("path", request.RequestURI), - zap.String("target", targetFilePath), + zap.String("response", targetFilePath), zap.Reflect("headers", request.Header), ) diff --git a/pkg/plugins/http_mock/protocol_options.go b/pkg/plugins/http_mock/protocol_options.go index 5e33e42..0c9f955 100644 --- a/pkg/plugins/http_mock/protocol_options.go +++ b/pkg/plugins/http_mock/protocol_options.go @@ -1,16 +1,27 @@ package main -import "github.com/spf13/viper" +import ( + "github.com/spf13/viper" + "regexp" +) const ( - rulesConfigKey = "rules" - patternConfigKey = "pattern" - targetConfigKey = "target" + rulesConfigKey = "rules" + patternConfigKey = "pattern" + responseConfigKey = "response" ) type targetRule struct { - pattern string - target string + pattern *regexp.Regexp + response string +} + +func (tr targetRule) Pattern() *regexp.Regexp { + return tr.pattern +} + +func (tr targetRule) Response() string { + return tr.response } type httpOptions struct { @@ -23,10 +34,15 @@ func loadFromConfig(config *viper.Viper) httpOptions { for _, i := range anonRules { innerData := i.(map[interface{}]interface{}) - options.Rules = append(options.Rules, targetRule{ - pattern: innerData[patternConfigKey].(string), - target: innerData[targetConfigKey].(string), - }) + + if rulePattern, err := regexp.Compile(innerData[patternConfigKey].(string)); err == nil { + options.Rules = append(options.Rules, targetRule{ + pattern: rulePattern, + response: innerData[responseConfigKey].(string), + }) + } else { + panic(err) + } } return options