Skip to content

Add configurable ip_source for client IP resolution.#429

Open
theredspoon wants to merge 2 commits intomatrix-construct:mainfrom
theredspoon:ip-source-configurable
Open

Add configurable ip_source for client IP resolution.#429
theredspoon wants to merge 2 commits intomatrix-construct:mainfrom
theredspoon:ip-source-configurable

Conversation

@theredspoon
Copy link
Copy Markdown

@theredspoon theredspoon commented Apr 21, 2026

Summary

Depends on #428 and should land after it.

Adds an optional ip_source config field for selecting how tuwunel resolves the client IP used by rate limiting, logging, and security tooling.

When ip_source is unset, no ConfiguredIpSource extension is installed, so the ClientIp extractor from #428 keeps the existing InsecureClientIp fallback behavior. When ip_source is set, the router installs a ConfiguredIpSource marker that maps to the selected SecureClientIpSource variant.

This also:

  • rejects runtime reload changes to ip_source, because the router layer stack is built at startup;
  • warns at startup when a header-based source is configured;
  • documents ip_source in tuwunel-example.toml with a valid commented example;
  • surfaces operator guidance in the generic deployment page and Caddy, Nginx, and Traefik reverse-proxy pages;
  • calls out that Unix-socket deployments should leave ip_source unset rather than setting connect_info;
  • teaches the config example generator a hidden config-example: directive, so fields can document an unset runtime default while emitting a valid example value;
  • removes syn clone assumptions in the macro helper path so the touched macro crate remains directly testable.

Macro note

tuwunel-example.toml is generated by config_example_generator, so a hand-edited example value would be overwritten on build. The new hidden config-example: directive lets ip_source keep its honest runtime default (unset / None) while generating the valid sample #ip_source = "connect_info".

The small src/macros/mod.rs and src/macros/implement.rs changes remove existing syn clone assumptions that surfaced when adding direct tests for the generator behavior. They are included so tuwunel_macros can be checked, tested, and linted directly with this PR.

Before / after

Before:

  • client IP resolution followed the existing fallback behavior from InsecureClientIp;
  • deployments behind trusted proxies had no typed config option for selecting a spoofing-resistant source;
  • changing the intended source at runtime had no explicit guard because no source setting existed.

After:

  • unset ip_source preserves the previous behavior;
  • ip_source = "connect_info" uses the TCP peer address;
  • header-based values such as rightmost_x_forwarded_for and cf_connecting_ip opt into the matching secure source and log a startup warning reminding operators to use a trusted reverse proxy;
  • deployment docs explain which source to use for direct TCP, Unix sockets, and trusted reverse proxies;
  • reload rejects ip_source changes until restart.

Closes #427.

Test plan

  • git diff --check
  • rustup run nightly rustfmt --check src/macros/config.rs src/macros/implement.rs src/macros/mod.rs src/core/config/mod.rs src/core/config/check.rs src/router/layers.rs
  • cargo test -p tuwunel_macros
  • cargo test -p tuwunel_core --no-default-features -- config::tests
  • cargo test -p tuwunel_router --no-default-features -- layers::tests
  • cargo check -p tuwunel_macros
  • cargo clippy -p tuwunel_macros -- -D warnings
  • cargo check -p tuwunel_core --no-default-features
  • cargo check -p tuwunel_router --no-default-features
  • cargo clippy -p tuwunel_core --no-default-features -- -D warnings
  • cargo clippy -p tuwunel_core --no-default-features --tests -- -D warnings
  • cargo clippy -p tuwunel_router --no-default-features -- -D warnings
  • cargo clippy -p tuwunel_router --no-default-features --tests -- -D warnings
  • lychee docs/deploying/generic.md docs/deploying/reverse-proxy-caddy.md docs/deploying/reverse-proxy-nginx.md docs/deploying/reverse-proxy-traefik.md (31 links checked, 0 errors, 3 redirects)
  • Confirmed the example remains #ip_source = "connect_info" and does not regenerate to #ip_source = unset.

On this local macOS checkout, router-targeted commands used CPLUS_INCLUDE_PATH=/Library/Developer/CommandLineTools/SDKs/MacOSX15.2.sdk/usr/include/c++/v1 because the local clang++ installation did not find libc++ headers by default.

@theredspoon theredspoon force-pushed the ip-source-configurable branch from c2b4a76 to 3d06e45 Compare April 21, 2026 21:41
@theredspoon theredspoon changed the title Add configurable ip_source for client IP resolution. (#427) Add configurable ip_source for client IP resolution. Apr 21, 2026
@theredspoon theredspoon force-pushed the ip-source-configurable branch 2 times, most recently from 3662b90 to e7a50bd Compare April 21, 2026 21:54
@theredspoon theredspoon marked this pull request as ready for review April 21, 2026 22:00
@theredspoon theredspoon force-pushed the ip-source-configurable branch 3 times, most recently from fdb0e1b to c2e9842 Compare April 22, 2026 09:47
@theredspoon theredspoon force-pushed the ip-source-configurable branch from c2e9842 to ed9fd3d Compare April 22, 2026 11:55
@theredspoon
Copy link
Copy Markdown
Author

Update after the restack:

Local validation performed:

  • Ran the Docker clippy bake target against this PR stack with the same relevant CI matrix shape: cargo_profiles=["test"], feat_sets=["all"], rust_toolchains=["nightly"], Debian testing-slim, Rust target x86_64-unknown-linux-gnu, sys target x86_64-v1-linux-gnu. It completed successfully with the CI -D warnings Clippy path. Local note: I set cargo_installs=cargo-chef for that Clippy run so the build skipped unrelated cargo helper installs and reached the lint step.
  • Ran cargo audit --stale --deny yanked --deny unsound --deny unmaintained --deny warnings --color never locally with cargo-audit 0.22.1; it exited successfully on this restacked head, inheriting Add ClientIp extractor and migrate handlers #428's rustls-webpki 0.103.13 lockfile fix.

Constraints / caveats:

  • The full Docker audit bake target did not complete locally because the Docker/Colima environment exhausted host disk space and then rustup hit Input/output error (os error 5) before the advisory scan. I used the host cargo-audit command above to verify the actual advisory condition instead.
  • Fresh GitHub Actions runs after the force-push are currently action_required with jobs: [], so upstream CI has not actually rerun yet and likely needs maintainer approval/re-run for the forked PR.

@theredspoon
Copy link
Copy Markdown
Author

#429 includes all of #428 plus the ip_source follow-up.

It passed the same rust-sdk-integ jobs that failed on #428, so those failures don’t look related to #428’s code.

Given that, my preferred path would be to merge #429 and close #428 as superseded.

If you’d rather preserve the two-PR split, I’d appreciate a rerun of #428 so you can merge them in order if it comes back green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Client IP spoofing: InsecureClientIp in all handlers allows forged IPs; add configurable ip_source

1 participant