mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
test(linux): Low-risk changes to prepare for Linux DNS support (#3625)
This splits off the easy parts from #3605. - Add quotes around `PHOENIX_SECURE_COOKIES` because my local `docker-compose` considers unquoted 'false' to be a schema error - Env vars are strings or numbers, not bools, it says - Create `test.httpbin.docker.local` container in a new subnet so it can be used as a DNS resource without the existing CIDR resource picking it up - Add resources and policies to `seeds.exs` per #3342 - Fix warning about `CONNLIB_LOG_UPLOAD_INTERVAL_SECS` not being set - Add `resolv-conf` dep and unit tests to `firezone-tunnel` and `firezone-linux-client` - Impl `on_disconnect` in the Linux client with `tracing::error!` - Add comments ```[tasklist] - [x] (failed) Confirm that the client container actually does stop faster this way - [x] Wait for tests to pass - [x] Mark as ready for review ```
This commit is contained in:
@@ -64,7 +64,7 @@ services:
|
||||
# Web Server
|
||||
EXTERNAL_URL: http://localhost:8080/
|
||||
PHOENIX_HTTP_WEB_PORT: "8080"
|
||||
PHOENIX_SECURE_COOKIES: false
|
||||
PHOENIX_SECURE_COOKIES: "false"
|
||||
# Erlang
|
||||
ERLANG_DISTRIBUTION_PORT: 9000
|
||||
ERLANG_CLUSTER_ADAPTER: "Elixir.Cluster.Strategy.Epmd"
|
||||
@@ -133,6 +133,8 @@ services:
|
||||
condition: "service_healthy"
|
||||
httpbin:
|
||||
condition: "service_healthy"
|
||||
test.httpbin.docker.local:
|
||||
condition: "service_healthy"
|
||||
iperf3:
|
||||
condition: "service_healthy"
|
||||
api:
|
||||
@@ -173,6 +175,7 @@ services:
|
||||
networks:
|
||||
app:
|
||||
ipv4_address: 172.28.0.105
|
||||
dns_resources:
|
||||
resources:
|
||||
|
||||
httpbin:
|
||||
@@ -183,6 +186,14 @@ services:
|
||||
resources:
|
||||
ipv4_address: 172.20.0.100
|
||||
|
||||
test.httpbin.docker.local:
|
||||
image: kennethreitz/httpbin
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "ps -C gunicorn"]
|
||||
networks:
|
||||
dns_resources:
|
||||
ipv4_address: 172.21.0.100
|
||||
|
||||
iperf3:
|
||||
image: networkstatic/iperf3
|
||||
healthcheck:
|
||||
@@ -249,7 +260,7 @@ services:
|
||||
# Web Server
|
||||
EXTERNAL_URL: http://localhost:8081/
|
||||
PHOENIX_HTTP_API_PORT: "8081"
|
||||
PHOENIX_SECURE_COOKIES: false
|
||||
PHOENIX_SECURE_COOKIES: "false"
|
||||
# Erlang
|
||||
ERLANG_DISTRIBUTION_PORT: 9000
|
||||
ERLANG_CLUSTER_ADAPTER: "Elixir.Cluster.Strategy.Epmd"
|
||||
@@ -357,6 +368,11 @@ services:
|
||||
# IPv6 is currently causing flakiness with GH actions and on our testbed.
|
||||
# Disabling until there's more time to debug.
|
||||
networks:
|
||||
# Using a separate subnet here so that the CIDR resource for 172.20.0.0 won't catch DNS resources
|
||||
dns_resources:
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.21.0.0/24
|
||||
resources:
|
||||
# enable_ipv6: true
|
||||
ipam:
|
||||
|
||||
@@ -679,6 +679,36 @@ IO.puts("")
|
||||
admin_subject
|
||||
)
|
||||
|
||||
{:ok, dns_httpbin_resource} =
|
||||
Resources.create_resource(
|
||||
%{
|
||||
type: :dns,
|
||||
name: "?.httpbin.docker.local",
|
||||
address: "?.httpbin.docker.local",
|
||||
address_description: "http://test.httpbin.docker.local/",
|
||||
connections: [%{gateway_group_id: gateway_group.id}],
|
||||
filters: [
|
||||
%{ports: ["80", "433"], protocol: :tcp},
|
||||
%{ports: ["53"], protocol: :udp},
|
||||
%{protocol: :icmp}
|
||||
]
|
||||
},
|
||||
admin_subject
|
||||
)
|
||||
|
||||
{:ok, dns_docker_resource} =
|
||||
Resources.create_resource(
|
||||
%{
|
||||
type: :dns,
|
||||
name: "*.docker.local",
|
||||
address: "*.docker.local",
|
||||
address_description: "*.docker.local/",
|
||||
connections: [%{gateway_group_id: gateway_group.id}],
|
||||
filters: [%{protocol: :all}]
|
||||
},
|
||||
admin_subject
|
||||
)
|
||||
|
||||
IO.puts("Created resources:")
|
||||
IO.puts(" #{dns_google_resource.address} - DNS - gateways: #{gateway_name}")
|
||||
IO.puts(" #{dns_gitlab_resource.address} - DNS - gateways: #{gateway_name}")
|
||||
@@ -687,6 +717,8 @@ IO.puts(" #{firezone_dev.address} - DNS - gateways: #{gateway_name}")
|
||||
IO.puts(" #{example_dns.address} - DNS - gateways: #{gateway_name}")
|
||||
IO.puts(" #{ip_resource.address} - IP - gateways: #{gateway_name}")
|
||||
IO.puts(" #{cidr_resource.address} - CIDR - gateways: #{gateway_name}")
|
||||
IO.puts(" #{dns_httpbin_resource.address} - DNS - gateways: #{gateway_name}")
|
||||
IO.puts(" #{dns_docker_resource.address} - DNS - gateways: #{gateway_name}")
|
||||
IO.puts("")
|
||||
|
||||
{:ok, _} =
|
||||
@@ -759,6 +791,26 @@ IO.puts("")
|
||||
admin_subject
|
||||
)
|
||||
|
||||
{:ok, _} =
|
||||
Policies.create_policy(
|
||||
%{
|
||||
name: "All Access To httpbin.docker.local",
|
||||
actor_group_id: everyone_group.id,
|
||||
resource_id: dns_httpbin_resource.id
|
||||
},
|
||||
admin_subject
|
||||
)
|
||||
|
||||
{:ok, _} =
|
||||
Policies.create_policy(
|
||||
%{
|
||||
name: "All Access To httpbin.docker.local",
|
||||
actor_group_id: everyone_group.id,
|
||||
resource_id: dns_docker_resource.id
|
||||
},
|
||||
admin_subject
|
||||
)
|
||||
|
||||
IO.puts("Policies Created")
|
||||
IO.puts("")
|
||||
|
||||
|
||||
2
rust/Cargo.lock
generated
2
rust/Cargo.lock
generated
@@ -2045,6 +2045,7 @@ dependencies = [
|
||||
"connlib-client-shared",
|
||||
"firezone-cli-utils",
|
||||
"humantime",
|
||||
"resolv-conf",
|
||||
"secrecy",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
@@ -2120,6 +2121,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"pnet_packet",
|
||||
"rand_core 0.6.4",
|
||||
"resolv-conf",
|
||||
"rtnetlink",
|
||||
"secrecy",
|
||||
"serde",
|
||||
|
||||
@@ -87,6 +87,7 @@ COPY . .
|
||||
|
||||
ARG TARGET
|
||||
ARG PACKAGE
|
||||
ENV CONNLIB_LOG_UPLOAD_INTERVAL_SECS=300
|
||||
RUN cargo build -p ${PACKAGE} $([ -n "${TARGET}" ] && "--target ${TARGET}")
|
||||
|
||||
# Image which is used to run the application binary
|
||||
|
||||
@@ -64,7 +64,10 @@ pub trait Callbacks: Clone + Send + Sync {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
/// Returns the system's default resolver
|
||||
/// Returns the system's default resolver(s)
|
||||
///
|
||||
/// It's okay for clients to include Firezone's own DNS here, e.g. 100.100.111.1.
|
||||
/// connlib internally filters them out.
|
||||
fn get_system_default_resolvers(&self) -> Result<Option<Vec<IpAddr>>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ hickory-resolver = { workspace = true }
|
||||
arc-swap = "1.6.0"
|
||||
bimap = "0.6"
|
||||
dns-lookup = { workspace = true }
|
||||
resolv-conf = "0.7.0"
|
||||
|
||||
# TODO: research replacing for https://github.com/algesten/str0m
|
||||
webrtc = { workspace = true }
|
||||
|
||||
@@ -91,7 +91,11 @@ impl Tun {
|
||||
utils::poll_raw_fd(&self.fd, |fd| read(fd, buf), cx)
|
||||
}
|
||||
|
||||
pub fn new(config: &InterfaceConfig, _: Vec<IpAddr>, _: &impl Callbacks) -> Result<Self> {
|
||||
pub fn new(
|
||||
config: &InterfaceConfig,
|
||||
dns_config: Vec<IpAddr>,
|
||||
_: &impl Callbacks,
|
||||
) -> Result<Self> {
|
||||
create_tun_device()?;
|
||||
|
||||
let fd = match unsafe { open(TUN_FILE.as_ptr() as _, O_RDWR) } {
|
||||
@@ -113,7 +117,9 @@ impl Tun {
|
||||
handle: handle.clone(),
|
||||
connection: join_handle,
|
||||
fd: AsyncFd::new(fd)?,
|
||||
worker: Mutex::new(Some(set_iface_config(config.clone(), handle).boxed())),
|
||||
worker: Mutex::new(Some(
|
||||
set_iface_config(config.clone(), dns_config, handle).boxed(),
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -190,7 +196,7 @@ impl Tun {
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(handle))]
|
||||
async fn set_iface_config(config: InterfaceConfig, handle: Handle) -> Result<()> {
|
||||
async fn set_iface_config(config: InterfaceConfig, _: Vec<IpAddr>, handle: Handle) -> Result<()> {
|
||||
let index = handle
|
||||
.link()
|
||||
.get()
|
||||
@@ -308,3 +314,106 @@ impl ioctl::Request<SetTunFlagsPayload> {
|
||||
struct SetTunFlagsPayload {
|
||||
flags: std::ffi::c_short,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
const DEBIAN_VM_RESOLV_CONF: &str = r#"
|
||||
# This is /run/systemd/resolve/stub-resolv.conf managed by man:systemd-resolved(8).
|
||||
# Do not edit.
|
||||
#
|
||||
# This file might be symlinked as /etc/resolv.conf. If you're looking at
|
||||
# /etc/resolv.conf and seeing this text, you have followed the symlink.
|
||||
#
|
||||
# This is a dynamic resolv.conf file for connecting local clients to the
|
||||
# internal DNS stub resolver of systemd-resolved. This file lists all
|
||||
# configured search domains.
|
||||
#
|
||||
# Run "resolvectl status" to see details about the uplink DNS servers
|
||||
# currently in use.
|
||||
#
|
||||
# Third party programs should typically not access this file directly, but only
|
||||
# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a
|
||||
# different way, replace this symlink by a static file or a different symlink.
|
||||
#
|
||||
# See man:systemd-resolved.service(8) for details about the supported modes of
|
||||
# operation for /etc/resolv.conf.
|
||||
nameserver 127.0.0.53
|
||||
options edns0 trust-ad
|
||||
search .
|
||||
"#;
|
||||
|
||||
// Docker seems to have injected the WSL host's resolv.conf into the Alpine container
|
||||
// Also the nameserver is changed for privacy
|
||||
const ALPINE_CONTAINER_RESOLV_CONF: &str = r#"
|
||||
# This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
|
||||
# [network]
|
||||
# generateResolvConf = false
|
||||
nameserver 9.9.9.9
|
||||
"#;
|
||||
|
||||
// From a Debian desktop
|
||||
const NETWORK_MANAGER_RESOLV_CONF: &str = r"
|
||||
# Generated by NetworkManager
|
||||
nameserver 192.168.1.1
|
||||
nameserver 2001:db8::%eno1
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn parse_resolv_conf() {
|
||||
let parsed = resolv_conf::Config::parse(DEBIAN_VM_RESOLV_CONF).unwrap();
|
||||
let mut config = resolv_conf::Config::new();
|
||||
config
|
||||
.nameservers
|
||||
.push(resolv_conf::ScopedIp::V4(Ipv4Addr::new(127, 0, 0, 53)));
|
||||
config.set_search(vec![".".into()]);
|
||||
config.edns0 = true;
|
||||
config.trust_ad = true;
|
||||
|
||||
assert_eq!(parsed, config);
|
||||
|
||||
let parsed = resolv_conf::Config::parse(ALPINE_CONTAINER_RESOLV_CONF).unwrap();
|
||||
let mut config = resolv_conf::Config::new();
|
||||
config
|
||||
.nameservers
|
||||
.push(resolv_conf::ScopedIp::V4(Ipv4Addr::new(9, 9, 9, 9)));
|
||||
|
||||
assert_eq!(parsed, config);
|
||||
|
||||
let parsed = resolv_conf::Config::parse(NETWORK_MANAGER_RESOLV_CONF).unwrap();
|
||||
let mut config = resolv_conf::Config::new();
|
||||
config
|
||||
.nameservers
|
||||
.push(resolv_conf::ScopedIp::V4(Ipv4Addr::new(192, 168, 1, 1)));
|
||||
config.nameservers.push(resolv_conf::ScopedIp::V6(
|
||||
Ipv6Addr::new(
|
||||
0x2001, 0x0db8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
|
||||
),
|
||||
Some("eno1".into()),
|
||||
));
|
||||
|
||||
assert_eq!(parsed, config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_resolv_conf() {
|
||||
let mut new_resolv_conf = resolv_conf::Config::new();
|
||||
for addr in ["100.100.111.1", "100.100.111.2"] {
|
||||
new_resolv_conf
|
||||
.nameservers
|
||||
.push(IpAddr::from_str(addr).unwrap().into());
|
||||
}
|
||||
|
||||
let actual = new_resolv_conf.to_string();
|
||||
assert_eq!(
|
||||
actual,
|
||||
r"nameserver 100.100.111.1
|
||||
nameserver 100.100.111.2
|
||||
"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ impl Tun {
|
||||
// using QUIC, HTTP/2, or even HTTP/1.1, and so they won't resolve the DNS
|
||||
// again unless you let that connection time out:
|
||||
// <https://github.com/firezone/firezone/issues/3113#issuecomment-1882096111>
|
||||
// TODO: If we have a Windows gateway, it shouldn't configure DNS, right?
|
||||
Command::new("powershell")
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.arg("-Command")
|
||||
|
||||
@@ -15,3 +15,4 @@ tracing = { workspace = true }
|
||||
clap = { version = "4.4", features = ["derive", "env"] }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
humantime = "2.1"
|
||||
resolv-conf = "0.7.0"
|
||||
|
||||
@@ -38,6 +38,14 @@ struct CallbackHandler {
|
||||
impl Callbacks for CallbackHandler {
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
fn on_disconnect(
|
||||
&self,
|
||||
error: Option<&connlib_client_shared::Error>,
|
||||
) -> Result<(), Self::Error> {
|
||||
tracing::error!(?error, "Disconnected");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn roll_log_file(&self) -> Option<PathBuf> {
|
||||
self.handle
|
||||
.as_ref()?
|
||||
|
||||
Reference in New Issue
Block a user