Files
firezone/.github/workflows/_rust.yml
Thomas Eizinger 99d8fcb8fc feat(connlib): resolve SRV & TXT queries for resources in sites (#8335)
## Description

We want to resolve DNS queries of type SRV & TXT for DNS resources
within the network context of the site that is hosting the DNS resource
itself. This allows admins to e.g. deploy dedicated nameservers into
those sites and have them resolve their SRV and TXT records to names
that are scoped to that particular site.

SRV records themselves return more domains which - if they are
configured as DNS resources - will be intercepted and then routed to the
correct site.

Prior to this PR, SRV & TXT records got resolved by the DNS server
configured on the client (or the server defined in the Firezone portal),
even if the domain in question was a DNS resource. This effectively
meant that those SRV records have to be valid globally and could not be
specific to the site that the DNS resource is hosted in.

## Example

Say we have these wildcard DNS resources:

- `**.department-a.example.com`
- `**.department-b.example.com`

Each of these DNS resources is assigned to a different site. If we now
issue an SRV DNS query to `_my-service.department-a.example.com`, we may
receive back the following records:

- `_my-service.department-a.example.com. 86400 IN SRV 10 60 8080
my-service1.department-a.example.com.`
- `_my-service.department-a.example.com. 86400 IN SRV 10 60 8080
my-service2.department-a.example.com.`
- `_my-service.department-a.example.com. 86400 IN SRV 10 60 8080
my-service3.department-a.example.com.`

Notice how the SRV records point to domains that will also match the
wildcard DNS resource above! If that is the case, Firezone will also
intercept A & AAAA queries for this service (which are a natural
follow-up from an application making an SRV query). As a result, traffic
for `my-service1.department-a.example.com` will be routed to the same
site the DNS resource is defined in. If the returned domains don't match
the wildcard DNS resource, the traffic will either not be intercepted at
all (if it is not a DNS resource) or routed to whichever site defines
the corresponding DNS resource.

All of these scenarios may be what the admin wants. If the SRV records
defined for the DNS resource are globally valid (and e.g. not even
resources), then resolving them using the Client's system resolver may
be all that is needed. If the services are running in a dedicated site,
that traffic should indeed be routed to that site.

As such, Firezone itself cannot make any assumption about the structure
of these records at all. The only thing that is enabled with this PR is
that IF the structure happens to match the same DNS resource, it allows
admins to deploy site-specific services that resolve their concrete
domains via SRV records.

## Testing

The implementation is tested using our property-based testing framework.
In order to cover these cases, we introduce the notion of site-specific
DNS records which are sampled when we create each individual Gateway.
When selecting a domain to query for, all global DNS records and the
site-specific ones are merged and a domain name and query type is chosen
at random.

At present, this testing framework does not assert that the DNS response
itself is correct, i.e. that it actually returned the site-specific
record. We don't assert this for any other DNS queries, hence this is
left for a future extension. We do assert using our regression grep's
that we hit the codepath of querying an SRV or TXT record for a DNS
resource.

Related: #8221
2025-03-04 12:41:32 +00:00

193 lines
7.2 KiB
YAML

---
name: Rust
"on":
workflow_call:
defaults:
run:
working-directory: ./rust
permissions:
contents: "read"
id-token: "write"
# Never tolerate warnings. Duplicated in `_tauri.yml`
env:
RUSTFLAGS: "-Dwarnings --cfg tokio_unstable"
RUSTDOCFLAGS: "-D warnings"
jobs:
bench:
name: bench-${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
runs-on: [
windows-2019, # Only platform with a benchmark right now
]
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/setup-rust
id: setup-rust
- run: cargo bench ${{ steps.setup-rust.outputs.bench-packages }}
env:
RUST_LOG: "debug"
name: "cargo bench"
shell: bash
static-analysis:
name: static-analysis-${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
# TODO: https://github.com/rust-lang/cargo/issues/5220
runs-on: [ubuntu-22.04, macos-14, windows-2022]
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/setup-rust
id: setup-rust
- uses: ./.github/actions/setup-tauri-v2
timeout-minutes: 5
- uses: taiki-e/install-action@0b63bc859f7224657cf7e39426848cabaa36f456 # v2.49.9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tool: cargo-udeps,cargo-deny
- run: |
rustup install --no-self-update nightly-2024-12-13 --profile minimal # The exact nightly version doesn't matter, just pin a random one.
cargo +nightly-2024-12-13 udeps --all-targets --all-features ${{ steps.setup-rust.outputs.packages }}
name: Check for unused dependencies
- run: cargo fmt -- --check
- run: cargo doc --all-features --no-deps --document-private-items ${{ steps.setup-rust.outputs.packages }}
name: "cargo doc"
shell: bash
- run: cargo clippy --all-targets --all-features ${{ steps.setup-rust.outputs.packages }}
name: "cargo clippy"
shell: bash
- run: cargo deny check --hide-inclusion-graph
shell: bash
test:
name: test-${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
# TODO: https://github.com/rust-lang/cargo/issues/5220
runs-on:
[
ubuntu-22.04,
ubuntu-24.04,
macos-13,
macos-14,
macos-15,
windows-2019,
windows-2022,
]
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/setup-rust
id: setup-rust
- uses: ./.github/actions/setup-tauri-v2
- uses: taiki-e/install-action@0b63bc859f7224657cf7e39426848cabaa36f456 # v2.49.9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tool: ripgrep
- name: "cargo test"
shell: bash
run: |
set -x
# First, run all tests.
cargo test --all-features ${{ steps.setup-rust.outputs.packages }} -- --include-ignored --nocapture
# Poor man's test coverage testing: Grep the generated logs for specific patterns / lines.
rg --count --no-ignore SendIcmpPacket $TESTCASES_DIR
rg --count --no-ignore SendUdpPacket $TESTCASES_DIR
rg --count --no-ignore SendTcpPayload $TESTCASES_DIR
rg --count --no-ignore SendDnsQueries $TESTCASES_DIR
rg --count --no-ignore "Packet for DNS resource" $TESTCASES_DIR
rg --count --no-ignore "Packet for CIDR resource" $TESTCASES_DIR
rg --count --no-ignore "Packet for Internet resource" $TESTCASES_DIR
rg --count --no-ignore "Performed IP-NAT46" $TESTCASES_DIR
rg --count --no-ignore "Performed IP-NAT64" $TESTCASES_DIR
rg --count --no-ignore "Too big DNS response, truncating" $TESTCASES_DIR
rg --count --no-ignore "Destination is unreachable" $TESTCASES_DIR
rg --count --no-ignore "Forwarding query for DNS resource to corresponding site" $TESTCASES_DIR
env:
# <https://github.com/rust-lang/cargo/issues/5999>
# Needed to create tunnel interfaces in unit tests
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: "sudo --preserve-env"
PROPTEST_VERBOSE: 0 # Otherwise the output is very long.
PROPTEST_CASES: 2000 # Default is only 256.
CARGO_PROFILE_TEST_OPT_LEVEL: 1 # Otherwise the tests take forever.
TESTCASES_DIR: "connlib/tunnel/testcases"
# Runs the Tauri client smoke test, built in debug mode. We can't run it in release
# mode because of a known issue: <https://github.com/firezone/firezone/blob/456e044f882c2bb314e19cc44c0d19c5ad817b7c/rust/windows-client/src-tauri/src/client.rs#L162-L164>
gui-smoke-test:
name: gui-smoke-test-${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
runs-on: [ubuntu-22.04, ubuntu-24.04, windows-2019, windows-2022]
runs-on: ${{ matrix.runs-on }}
defaults:
run:
# Must be in this dir for `pnpm` to work
working-directory: ./rust/gui-client
# The Windows client ignores RUST_LOG because it uses a settings file instead
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/setup-node
- uses: ./.github/actions/setup-rust
- uses: ./.github/actions/setup-tauri-v2
timeout-minutes: 5
with:
runtime: true
# These steps must be synchronized with build.sh and build.bat in `rust/gui-client`
- name: pnpm install
run: |
pnpm install
cp "node_modules/flowbite/dist/flowbite.min.js" "src/"
- name: Compile Tailwind
run: pnpm tailwindcss -i src/input.css -o src/output.css
- name: Run Vite bundler
run: pnpm vite build
- name: Build client
run: cargo build -p firezone-gui-client --all-targets
- uses: taiki-e/install-action@0b63bc859f7224657cf7e39426848cabaa36f456 # v2.49.9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tool: dump_syms
- name: Run smoke test
working-directory: ./rust
run: cargo run -p gui-smoke-test
headless-client:
name: headless-client-${{ matrix.test }}-${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
include:
- { runs-on: windows-2019, test: token-path-windows.ps1 }
- { runs-on: windows-2022, test: token-path-windows.ps1 }
- { runs-on: ubuntu-22.04, test: linux-group.sh }
- { runs-on: ubuntu-24.04, test: linux-group.sh }
- { runs-on: ubuntu-22.04, test: token-path-linux.sh }
- { runs-on: ubuntu-24.04, test: token-path-linux.sh }
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/setup-rust
- uses: ./.github/actions/setup-tauri-v2
timeout-minutes: 5
- run: scripts/tests/${{ matrix.test }}
name: "test script"
working-directory: ./