Skip to content

Commit

Permalink
Extend dns config with a local/remote filter (#2600)
Browse files Browse the repository at this point in the history
* Config updated

* wip

* Implemented DNS selector in the layer

* DnsConfig verification

* Updated docs, schema, configuration.md

* Fixed docs, updated config printout task

* Changelog

* format

* fixed docs

* Fixed selector logic

* Fix docs again

* Added filters to top-level config doc

* schema udpated

* configuration.md updated

* Fixed error message

* Updated DNS doc to mention limited support

* Fixed example in doc

* Improve error variant

* Fix filter parsing

* Update query matching in DnsSelector

* Fixed parsing

* Added verification for outgoing filters as well

* Fixed matching in outgoing selector
  • Loading branch information
Razz4780 authored Jul 17, 2024
1 parent 1323a14 commit e03ed77
Show file tree
Hide file tree
Showing 18 changed files with 1,025 additions and 467 deletions.
1 change: 1 addition & 0 deletions changelog.d/2579.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a bug where enabling remote DNS prevented making a local connection with telnet.
1 change: 1 addition & 0 deletions changelog.d/2581.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Extended `feature.network.dns` config with an optional local/remote filter, following `feature.network.outgoing` pattern.
81 changes: 75 additions & 6 deletions mirrord-schema.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "LayerFileConfig",
"description": "mirrord allows for a high degree of customization when it comes to which features you want to enable, and how they should function.\n\nAll of the configuration fields have a default value, so a minimal configuration would be no configuration at all.\n\nThe configuration supports templating using the [Tera](https://keats.github.io/tera/docs/) template engine. Currently we don't provide additional values to the context, if you have anything you want us to provide please let us know.\n\nTo use a configuration file in the CLI, use the `-f <CONFIG_PATH>` flag. Or if using VSCode Extension or JetBrains plugin, simply create a `.mirrord/mirrord.json` file or use the UI.\n\nTo help you get started, here are examples of a basic configuration file, and a complete configuration file containing all fields.\n\n### Basic `config.json` {#root-basic}\n\n```json { \"target\": \"pod/bear-pod\", \"feature\": { \"env\": true, \"fs\": \"read\", \"network\": true } } ```\n\n### Basic `config.json` with templating {#root-basic-templating}\n\n```json { \"target\": \"{{ get_env(name=\"TARGET\", default=\"pod/fallback\") }}\", \"feature\": { \"env\": true, \"fs\": \"read\", \"network\": true } } ```\n\n### Complete `config.json` {#root-complete}\n\nDon't use this example as a starting point, it's just here to show you all the available options. ```json { \"accept_invalid_certificates\": false, \"skip_processes\": \"ide-debugger\", \"target\": { \"path\": \"pod/bear-pod\", \"namespace\": \"default\" }, \"connect_tcp\": null, \"agent\": { \"log_level\": \"info\", \"json_log\": false, \"labels\": { \"user\": \"meow\" }, \"annotations\": { \"cats.io/inject\": \"enabled\" }, \"namespace\": \"default\", \"image\": \"ghcr.io/metalbear-co/mirrord:latest\", \"image_pull_policy\": \"IfNotPresent\", \"image_pull_secrets\": [ { \"secret-key\": \"secret\" } ], \"ttl\": 30, \"ephemeral\": false, \"communication_timeout\": 30, \"startup_timeout\": 360, \"network_interface\": \"eth0\", \"flush_connections\": true }, \"feature\": { \"env\": { \"include\": \"DATABASE_USER;PUBLIC_ENV\", \"exclude\": \"DATABASE_PASSWORD;SECRET_ENV\", \"override\": { \"DATABASE_CONNECTION\": \"db://localhost:7777/my-db\", \"LOCAL_BEAR\": \"panda\" } }, \"fs\": { \"mode\": \"write\", \"read_write\": \".+\\\\.json\" , \"read_only\": [ \".+\\\\.yaml\", \".+important-file\\\\.txt\" ], \"local\": [ \".+\\\\.js\", \".+\\\\.mjs\" ] }, \"network\": { \"incoming\": { \"mode\": \"steal\", \"http_filter\": { \"header_filter\": \"host: api\\\\..+\" }, \"port_mapping\": [[ 7777, 8888 ]], \"ignore_localhost\": false, \"ignore_ports\": [9999, 10000] }, \"outgoing\": { \"tcp\": true, \"udp\": true, \"filter\": { \"local\": [\"tcp://1.1.1.0/24:1337\", \"1.1.5.0/24\", \"google.com\", \":53\"] }, \"ignore_localhost\": false, \"unix_streams\": \"bear.+\" }, \"dns\": false }, \"copy_target\": { \"scale_down\": false } }, \"operator\": true, \"kubeconfig\": \"~/.kube/config\", \"sip_binaries\": \"bash\", \"telemetry\": true, \"kube_context\": \"my-cluster\" } ```\n\n# Options {#root-options}",
"description": "mirrord allows for a high degree of customization when it comes to which features you want to enable, and how they should function.\n\nAll of the configuration fields have a default value, so a minimal configuration would be no configuration at all.\n\nThe configuration supports templating using the [Tera](https://keats.github.io/tera/docs/) template engine. Currently we don't provide additional values to the context, if you have anything you want us to provide please let us know.\n\nTo use a configuration file in the CLI, use the `-f <CONFIG_PATH>` flag. Or if using VSCode Extension or JetBrains plugin, simply create a `.mirrord/mirrord.json` file or use the UI.\n\nTo help you get started, here are examples of a basic configuration file, and a complete configuration file containing all fields.\n\n### Basic `config.json` {#root-basic}\n\n```json { \"target\": \"pod/bear-pod\", \"feature\": { \"env\": true, \"fs\": \"read\", \"network\": true } } ```\n\n### Basic `config.json` with templating {#root-basic-templating}\n\n```json { \"target\": \"{{ get_env(name=\"TARGET\", default=\"pod/fallback\") }}\", \"feature\": { \"env\": true, \"fs\": \"read\", \"network\": true } } ```\n\n### Complete `config.json` {#root-complete}\n\nDon't use this example as a starting point, it's just here to show you all the available options. ```json { \"accept_invalid_certificates\": false, \"skip_processes\": \"ide-debugger\", \"target\": { \"path\": \"pod/bear-pod\", \"namespace\": \"default\" }, \"connect_tcp\": null, \"agent\": { \"log_level\": \"info\", \"json_log\": false, \"labels\": { \"user\": \"meow\" }, \"annotations\": { \"cats.io/inject\": \"enabled\" }, \"namespace\": \"default\", \"image\": \"ghcr.io/metalbear-co/mirrord:latest\", \"image_pull_policy\": \"IfNotPresent\", \"image_pull_secrets\": [ { \"secret-key\": \"secret\" } ], \"ttl\": 30, \"ephemeral\": false, \"communication_timeout\": 30, \"startup_timeout\": 360, \"network_interface\": \"eth0\", \"flush_connections\": true }, \"feature\": { \"env\": { \"include\": \"DATABASE_USER;PUBLIC_ENV\", \"exclude\": \"DATABASE_PASSWORD;SECRET_ENV\", \"override\": { \"DATABASE_CONNECTION\": \"db://localhost:7777/my-db\", \"LOCAL_BEAR\": \"panda\" } }, \"fs\": { \"mode\": \"write\", \"read_write\": \".+\\\\.json\" , \"read_only\": [ \".+\\\\.yaml\", \".+important-file\\\\.txt\" ], \"local\": [ \".+\\\\.js\", \".+\\\\.mjs\" ] }, \"network\": { \"incoming\": { \"mode\": \"steal\", \"http_filter\": { \"header_filter\": \"host: api\\\\..+\" }, \"port_mapping\": [[ 7777, 8888 ]], \"ignore_localhost\": false, \"ignore_ports\": [9999, 10000] }, \"outgoing\": { \"tcp\": true, \"udp\": true, \"filter\": { \"local\": [\"tcp://1.1.1.0/24:1337\", \"1.1.5.0/24\", \"google.com\", \":53\"] }, \"ignore_localhost\": false, \"unix_streams\": \"bear.+\" }, \"dns\": { \"enabled\": true, \"filter\": { \"local\": [\"1.1.1.0/24:1337\", \"1.1.5.0/24\", \"google.com\"] } } }, \"copy_target\": { \"scale_down\": false } }, \"operator\": true, \"kubeconfig\": \"~/.kube/config\", \"sip_binaries\": \"bash\", \"telemetry\": true, \"kube_context\": \"my-cluster\" } ```\n\n# Options {#root-options}",
"type": "object",
"properties": {
"accept_invalid_certificates": {
Expand Down Expand Up @@ -550,6 +550,62 @@
},
"additionalProperties": false
},
"DnsFileConfig": {
"description": "Resolve DNS via the remote pod.\n\nDefaults to `true`.\n\nMind that: - DNS resolving can be done in multiple ways. Some frameworks use `getaddrinfo`/`gethostbyname` functions, while others communicate directly with the DNS server at port `53` and perform a sort of manual resolution. Just enabling the `dns` feature in mirrord might not be enough. If you see an address resolution error, try enabling the [`fs`](#feature-fs) feature, and setting `read_only: [\"/etc/resolv.conf\"]`. - DNS filter currently works only with frameworks that use `getaddrinfo`/`gethostbyname` functions.",
"type": "object",
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
},
"filter": {
"title": "feature.network.dns.filter {#feature-network-dns-filter}",
"description": "Unstable: the precise syntax of this config is subject to change.",
"anyOf": [
{
"$ref": "#/definitions/DnsFilterConfig"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
},
"DnsFilterConfig": {
"description": "List of addresses/ports/subnets that should be resolved through either the remote pod or local app, depending how you set this up with either `remote` or `local`.\n\nYou may use this option to specify when DNS resolution is done from the remote pod (which is the default behavior when you enable remote DNS), or from the local app (default when you have remote DNS disabled).\n\nTakes a list of values, such as:\n\n- Only queries for hostname `my-service-in-cluster` will go through the remote pod.\n\n```json { \"remote\": [\"my-service-in-cluster\"] } ```\n\n- Only queries for addresses in subnet `1.1.1.0/24` with service port `1337`` will go through the remote pod.\n\n```json { \"remote\": [\"1.1.1.0/24:1337\"] } ```\n\n- Only queries for hostname `google.com` with service port `1337` or `7331` will go through the remote pod.\n\n```json { \"remote\": [\"google.com:1337\", \"google.com:7331\"] } ```\n\n- Only queries for `localhost` with service port `1337` will go through the local app.\n\n```json { \"local\": [\"localhost:1337\"] } ```\n\n- Only queries with service port `1337` or `7331` will go through the local app.\n\n```json { \"local\": [\":1337\", \":7331\"] } ```\n\nValid values follow this pattern: `[name|address|subnet/mask][:port]`.",
"oneOf": [
{
"description": "DNS queries matching what is specified here will go through the remote pod, everything else will go through local.",
"type": "object",
"required": [
"remote"
],
"properties": {
"remote": {
"$ref": "#/definitions/VecOrSingle_for_String"
}
},
"additionalProperties": false
},
{
"description": "DNS queries matching what is specified here will go through the local app, everything else will go through the remote pod.",
"type": "object",
"required": [
"local"
],
"properties": {
"local": {
"$ref": "#/definitions/VecOrSingle_for_String"
}
},
"additionalProperties": false
}
]
},
"EnvFileConfig": {
"description": "Allows the user to set or override the local process' environment variables with the ones from the remote pod.\n\nWhich environment variables to load from the remote pod are controlled by setting either [`include`](#feature-env-include) or [`exclude`](#feature-env-exclude).\n\nSee the environment variables [reference](https://mirrord.dev/docs/reference/env/) for more details.\n\n```json { \"feature\": { \"env\": { \"include\": \"DATABASE_USER;PUBLIC_ENV;MY_APP_*\", \"exclude\": \"DATABASE_PASSWORD;SECRET_ENV\", \"override\": { \"DATABASE_CONNECTION\": \"db://localhost:7777/my-db\", \"LOCAL_BEAR\": \"panda\" } } } } ```",
"type": "object",
Expand Down Expand Up @@ -1067,15 +1123,18 @@
]
},
"NetworkFileConfig": {
"description": "Controls mirrord network operations.\n\nSee the network traffic [reference](https://mirrord.dev/docs/reference/traffic/) for more details.\n\n```json { \"feature\": { \"network\": { \"incoming\": { \"mode\": \"steal\", \"http_filter\": { \"header_filter\": \"host: api\\\\..+\" }, \"port_mapping\": [[ 7777, 8888 ]], \"ignore_localhost\": false, \"ignore_ports\": [9999, 10000] }, \"outgoing\": { \"tcp\": true, \"udp\": true, \"filter\": { \"local\": [\"tcp://1.1.1.0/24:1337\", \"1.1.5.0/24\", \"google.com\", \":53\"] }, \"ignore_localhost\": false, \"unix_streams\": \"bear.+\" }, \"dns\": false } } } ```",
"description": "Controls mirrord network operations.\n\nSee the network traffic [reference](https://mirrord.dev/docs/reference/traffic/) for more details.\n\n```json { \"feature\": { \"network\": { \"incoming\": { \"mode\": \"steal\", \"http_filter\": { \"header_filter\": \"host: api\\\\..+\" }, \"port_mapping\": [[ 7777, 8888 ]], \"ignore_localhost\": false, \"ignore_ports\": [9999, 10000] }, \"outgoing\": { \"tcp\": true, \"udp\": true, \"filter\": { \"local\": [\"tcp://1.1.1.0/24:1337\", \"1.1.5.0/24\", \"google.com\", \":53\"] }, \"ignore_localhost\": false, \"unix_streams\": \"bear.+\" }, \"dns\": { \"enabled\": true, \"filter\": { \"local\": [\"1.1.1.0/24:1337\", \"1.1.5.0/24\", \"google.com\"] } } } } } ```",
"type": "object",
"properties": {
"dns": {
"title": "feature.network.dns {#feature-network-dns}",
"description": "Resolve DNS via the remote pod.\n\nDefaults to `true`.\n\n- Caveats: DNS resolving can be done in multiple ways, some frameworks will use `getaddrinfo`, while others will create a connection on port `53` and perform a sort of manual resolution. Just enabling the `dns` feature in mirrord might not be enough. If you see an address resolution error, try enabling the [`fs`](#feature-fs) feature, and setting `read_only: [\"/etc/resolv.conf\"]`.",
"type": [
"boolean",
"null"
"anyOf": [
{
"$ref": "#/definitions/ToggleableConfig_for_DnsFileConfig"
},
{
"type": "null"
}
]
},
"incoming": {
Expand Down Expand Up @@ -1353,6 +1412,16 @@
}
]
},
"ToggleableConfig_for_DnsFileConfig": {
"anyOf": [
{
"type": "boolean"
},
{
"$ref": "#/definitions/DnsFileConfig"
}
]
},
"ToggleableConfig_for_EnvFileConfig": {
"anyOf": [
{
Expand Down
33 changes: 29 additions & 4 deletions mirrord/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ use mirrord_analytics::{
};
use mirrord_config::{
config::{ConfigContext, MirrordConfig},
feature::{fs::FsModeConfig, network::incoming::IncomingMode},
feature::{
fs::FsModeConfig,
network::{
dns::{DnsConfig, DnsFilterConfig},
incoming::IncomingMode,
},
},
target::TargetDisplay,
LayerConfig, LayerFileConfig,
};
Expand Down Expand Up @@ -269,9 +275,28 @@ fn print_config<P>(
};
messages.push(format!("outgoing: forwarding is {}", outgoing_info));

let dns_info = match config.feature.network.dns {
true => "remotely",
false => "locally",
let dns_info = match &config.feature.network.dns {
DnsConfig { enabled: false, .. } => "locally",
DnsConfig {
enabled: true,
filter: None,
} => "remotely",
DnsConfig {
enabled: true,
filter: Some(DnsFilterConfig::Remote(filters)),
} if filters.is_empty() => "locally",
DnsConfig {
enabled: true,
filter: Some(DnsFilterConfig::Local(filters)),
} if filters.is_empty() => "remotely",
DnsConfig {
enabled: true,
filter: Some(DnsFilterConfig::Remote(..)),
} => "locally with exceptions",
DnsConfig {
enabled: true,
filter: Some(DnsFilterConfig::Local(..)),
} => "remotely with exceptions",
};
messages.push(format!("dns: DNS will be resolved {}", dns_info));

Expand Down
92 changes: 83 additions & 9 deletions mirrord/config/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ configuration file containing all fields.
"ignore_localhost": false,
"unix_streams": "bear.+"
},
"dns": false
"dns": {
"enabled": true,
"filter": {
"local": ["1.1.1.0/24:1337", "1.1.5.0/24", "google.com"]
}
}
},
"copy_target": {
"scale_down": false
Expand Down Expand Up @@ -382,14 +387,18 @@ IP:PORT to connect to instead of using k8s api, for testing purposes.
mirrord Experimental features.
This shouldn't be used unless someone from MetalBear/mirrord tells you to.

## _experimental_ readlink {#fexperimental-readlink}
## _experimental_ readlink {#experimental-readlink}

Enables the `readlink` hook.

## _experimental_ tcp_ping4_mock {#fexperimental-tcp_ping4_mock}
## _experimental_ tcp_ping4_mock {#experimental-tcp_ping4_mock}

<https://github.com/metalbear-co/mirrord/issues/2421#issuecomment-2093200904>

# _experimental_ trust_any_certificate {#experimental-trust_any_certificate}

Enables trusting any certificate on macOS, useful for <https://github.com/golang/go/issues/51991#issuecomment-2059588252>

# feature {#root-feature}

Controls mirrord features.
Expand Down Expand Up @@ -686,7 +695,12 @@ for more details.
"ignore_localhost": false,
"unix_streams": "bear.+"
},
"dns": false
"dns": {
"enabled": true,
"filter": {
"local": ["1.1.1.0/24:1337", "1.1.5.0/24", "google.com"]
}
}
}
}
}
Expand All @@ -698,11 +712,71 @@ Resolve DNS via the remote pod.

Defaults to `true`.

- Caveats: DNS resolving can be done in multiple ways, some frameworks will use
`getaddrinfo`, while others will create a connection on port `53` and perform a sort
of manual resolution. Just enabling the `dns` feature in mirrord might not be enough.
If you see an address resolution error, try enabling the [`fs`](#feature-fs) feature,
and setting `read_only: ["/etc/resolv.conf"]`.
Mind that:
- DNS resolving can be done in multiple ways. Some frameworks use
`getaddrinfo`/`gethostbyname` functions, while others communicate directly with the DNS server
at port `53` and perform a sort of manual resolution. Just enabling the `dns` feature in mirrord
might not be enough. If you see an address resolution error, try enabling the
[`fs`](#feature-fs) feature, and setting `read_only: ["/etc/resolv.conf"]`.
- DNS filter currently works only with frameworks that use `getaddrinfo`/`gethostbyname`
functions.

#### feature.network.dns.filter {#feature-network-dns-filter}

Unstable: the precise syntax of this config is subject to change.

List of addresses/ports/subnets that should be resolved through either the remote pod or local
app, depending how you set this up with either `remote` or `local`.

You may use this option to specify when DNS resolution is done from the remote pod (which
is the default behavior when you enable remote DNS), or from the local app (default when
you have remote DNS disabled).

Takes a list of values, such as:

- Only queries for hostname `my-service-in-cluster` will go through the remote pod.

```json
{
"remote": ["my-service-in-cluster"]
}
```

- Only queries for addresses in subnet `1.1.1.0/24` with service port `1337`` will go through
the remote pod.

```json
{
"remote": ["1.1.1.0/24:1337"]
}
```

- Only queries for hostname `google.com` with service port `1337` or `7331`
will go through the remote pod.

```json
{
"remote": ["google.com:1337", "google.com:7331"]
}
```

- Only queries for `localhost` with service port `1337` will go through the local app.

```json
{
"local": ["localhost:1337"]
}
```

- Only queries with service port `1337` or `7331` will go through the local app.

```json
{
"local": [":1337", ":7331"]
}
```

Valid values follow this pattern: `[name|address|subnet/mask][:port]`.

### feature.network.incoming {#feature-network-incoming}

Expand Down
11 changes: 9 additions & 2 deletions mirrord/config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,15 @@ pub enum ConfigError {
#[error("value for {1:?} not provided in {0:?} (env override {2:?})")]
ValueNotProvided(&'static str, &'static str, Option<&'static str>),

#[error("value {0:?} for {1:?} is invalid.")]
InvalidValue(String, &'static str),
#[error("invalid {} value `{}`: {}", .name, .provided, .error)]
InvalidValue {
// Name of parsed env var or field path in the config.
name: &'static str,
// Value provided by the user.
provided: String,
// Error that occurred when processing the value.
error: Box<dyn Error + Send + Sync>,
},

#[error("mirrord-config: IO operation failed with `{0}`")]
Io(#[from] std::io::Error),
Expand Down
Loading

0 comments on commit e03ed77

Please sign in to comment.