diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 3e5956d9..3f4cd5f6 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -21,6 +21,7 @@ jobs: - self-hosted - Linux - ${{ matrix.runner }} + strategy: matrix: cpu: [arm64, amd64, arm/v7] @@ -34,23 +35,31 @@ jobs: - cpu: arm/v7 runner: ARM tag: armv7 + + permissions: + contents: read + packages: write + steps: - name: Checkout uses: actions/checkout@v4 with: submodules: recursive + - name: Login to GitHub container registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: buildkitd-config-inline: | [registry."docker.io"] mirrors = ["dockerhub-proxy.teonite.net"] + - name: Build container uses: docker/build-push-action@v5 with: @@ -62,10 +71,30 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max + - name: Scan image with Trivy + uses: aquasecurity/trivy-action@0.32.0 + with: + image-ref: "${{ env.GHCR_REPO }}:${{ github.sha }}-${{ matrix.tag }}" + format: "table" + exit-code: "1" + ignore-unfixed: true + vuln-type: "os,library" + severity: "CRITICAL,HIGH,MEDIUM" + docker-manifest: runs-on: [self-hosted, Linux] + + permissions: + contents: read + packages: write + id-token: write # needed for signing the images with GitHub OIDC Token + needs: [build-docker] + steps: + - name: Install Cosign + uses: sigstore/cosign-installer@v3.9.2 + - name: Docker meta id: meta uses: docker/metadata-action@v5 @@ -74,12 +103,14 @@ jobs: ${{ env.GHCR_REPO }} flavor: ${{ inputs.flavor }} tags: ${{ inputs.tags }} + - name: Login to GitHub container registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Create and push manifests run: | tags='${{ env.GHCR_REPO }}:${{ github.sha }} ${{ steps.meta.outputs.tags }}' @@ -89,3 +120,13 @@ jobs: docker manifest create ${tag} ${{ env.GHCR_REPO }}:${{ github.sha }}-amd64 ${{ env.GHCR_REPO }}:${{ github.sha }}-arm64 ${{ env.GHCR_REPO }}:${{ github.sha }}-armv7 docker manifest push ${tag} done + + - name: Sign the images with GitHub OIDC Token + run: | + images='${{ env.GHCR_REPO }}:${{ github.sha }} ${{ steps.meta.outputs.tags }}' + cosign sign --yes ${images} + + - name: Verify image signatures + run: | + images='${{ env.GHCR_REPO }}:${{ github.sha }} ${{ steps.meta.outputs.tags }}' + cosign verify ${images} --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp="https://github.com/DefGuard/gateway" -o text diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16ba8185..cc9a2889 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: branches: - main - dev - - 'release/**' + - "release/**" paths-ignore: - "*.md" - "LICENSE" @@ -13,7 +13,7 @@ on: branches: - main - dev - - 'release/**' + - "release/**" paths-ignore: - "*.md" - "LICENSE" @@ -23,31 +23,38 @@ env: jobs: test: - runs-on: [self-hosted, Linux, X64] - container: rust:1 + runs-on: + - codebuild-defguard-gateway-runner-${{ github.run_id }}-${{ github.run_attempt }} + container: public.ecr.aws/docker/library/rust:1 steps: - - name: Debug - run: echo ${{ github.ref_name }} - name: Checkout uses: actions/checkout@v4 with: submodules: recursive + - name: Cache uses: Swatinem/rust-cache@v2 with: key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Install dependencies run: apt-get update && apt-get -y install protobuf-compiler libnftnl-dev libmnl-dev + - name: Check format run: | rustup component add rustfmt cargo fmt -- --check + - name: Run clippy linter run: | rustup component add clippy cargo clippy --all-targets --all-features -- -D warnings + - name: Run cargo deny - uses: EmbarkStudios/cargo-deny-action@v2 + run: | + cargo install cargo-deny + cargo deny check + - name: Run tests run: cargo test --locked --no-fail-fast diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 6c8f9745..00000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: rustdoc Github Pages -on: - push: - branches: - - main - -env: - CARGO_INCREMENTAL: 0 - CARGO_NET_RETRY: 10 - RUSTFLAGS: "-D warnings -W unreachable-pub" - RUSTUP_MAX_RETRIES: 10 - -jobs: - rustdoc: - runs-on: [self-hosted, Linux] - container: - image: rust:1 - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Rust toolchain - run: rustup update --no-self-update stable - - - name: Install dependencies - run: apt-get update && apt-get -y install protobuf-compiler libnftnl-dev libmnl-dev - - - name: Build Docs - run: cargo doc --all --no-deps - - - name: Deploy Docs - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_branch: gh-pages - publish_dir: ./target/doc - force_orphan: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3e7fb87..0977caef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -98,6 +98,10 @@ jobs: target: ${{ matrix.target }} override: true + - name: Setup `packer` + uses: hashicorp/setup-packer@main + id: setup + - name: Build release binary uses: actions-rs/cargo@v1 with: @@ -144,6 +148,26 @@ jobs: asset_name: defguard-gateway_${{ env.VERSION }}_${{ matrix.target }}.deb asset_content_type: application/octet-stream + - name: Run `packer init` + if: matrix.build == 'linux' && matrix.arch == 'amd64' + id: init + run: "packer init ./images/ami/gateway.pkr.hcl" + + - name: Build AMI images for multiple regions + if: matrix.build == 'linux' && matrix.arch == 'amd64' + run: | + regions=(us-east-1 eu-west-1 ap-northeast-1 eu-central-1) + for region in "${regions[@]}"; do + echo "Building AMI for region: $region" + echo "Running packer validate for $region..." + packer validate --var "package_version=${{ env.VERSION }}" --var "region=$region" ./images/ami/gateway.pkr.hcl + echo "Building AMI image for $region..." + packer build -color=false -on-error=abort --var "package_version=${{ env.VERSION }}" --var "region=$region" ./images/ami/gateway.pkr.hcl + done + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - name: Build RPM package if: matrix.build == 'linux' uses: defGuard/fpm-action@main diff --git a/Cargo.lock b/Cargo.lock index 2d7c659a..210b9d7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -58,57 +58,35 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -127,41 +105,13 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core 0.4.5", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "itoa", - "matchit 0.7.3", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper", - "tower 0.5.2", - "tower-layer", - "tower-service", -] - [[package]] name = "axum" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ - "axum-core 0.5.2", - "axum-macros", + "axum-core", "bytes", "form_urlencoded", "futures-util", @@ -171,7 +121,7 @@ dependencies = [ "hyper", "hyper-util", "itoa", - "matchit 0.8.4", + "matchit", "memchr", "mime", "percent-encoding", @@ -183,32 +133,12 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tower 0.5.2", + "tower", "tower-layer", "tower-service", "tracing", ] -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", -] - [[package]] name = "axum-core" version = "0.5.2" @@ -229,17 +159,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "backtrace" version = "0.3.75" @@ -263,9 +182,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "byteorder" @@ -281,10 +200,11 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -292,9 +212,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -304,9 +224,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.40" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" dependencies = [ "clap_builder", "clap_derive", @@ -314,9 +234,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" dependencies = [ "anstream", "anstyle", @@ -326,9 +246,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ "heck", "proc-macro2", @@ -375,9 +295,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -445,11 +365,12 @@ dependencies = [ [[package]] name = "defguard-gateway" -version = "1.4.1" +version = "1.5.0" dependencies = [ - "axum 0.8.4", + "axum", "base64", "clap", + "defguard_version", "defguard_wireguard_rs", "env_logger", "gethostname", @@ -462,20 +383,41 @@ dependencies = [ "prost", "serde", "syslog", - "thiserror 2.0.12", + "thiserror", "tokio", "tokio-stream", "toml", "tonic", - "tonic-build", + "tonic-prost", + "tonic-prost-build", + "tower", + "tracing", "vergen-git2", "x25519-dalek", ] +[[package]] +name = "defguard_version" +version = "0.0.0" +source = "git+https://github.com/DefGuard/defguard.git?rev=c8e1bc91e0410a91af0b124d2d2b57c5a7226517#c8e1bc91e0410a91af0b124d2d2b57c5a7226517" +dependencies = [ + "axum", + "http", + "os_info", + "semver", + "serde", + "thiserror", + "tonic", + "tower", + "tracing", + "tracing-subscriber", +] + [[package]] name = "defguard_wireguard_rs" -version = "0.7.5" -source = "git+https://github.com/DefGuard/wireguard-rs.git?rev=v0.7.5#d090d2249e5bb3d4154f07de098387e2ab69bfdc" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093cede63322e14eede3916a6a5de2518788f438a6cdfc71d262c72d0ae865d0" dependencies = [ "base64", "libc", @@ -488,15 +430,15 @@ dependencies = [ "netlink-sys", "nix", "serde", - "thiserror 2.0.12", + "thiserror", "x25519-dalek", ] [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" dependencies = [ "powerfmt", ] @@ -580,12 +522,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -600,6 +542,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + [[package]] name = "fixedbitset" version = "0.5.7" @@ -624,9 +572,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -700,7 +648,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.5+wasi-0.2.4", ] [[package]] @@ -724,9 +672,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -734,7 +682,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.10.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -743,15 +691,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "heck" @@ -767,7 +709,7 @@ checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" dependencies = [ "cfg-if", "libc", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -818,13 +760,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -832,6 +775,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -852,9 +796,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "bytes", "futures-channel", @@ -965,9 +909,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -986,22 +930,23 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "equivalent", + "hashbrown", ] [[package]] -name = "indexmap" -version = "2.10.0" +name = "io-uring" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "equivalent", - "hashbrown 0.15.4", + "bitflags", + "cfg-if", + "libc", ] [[package]] @@ -1057,19 +1002,25 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libgit2-sys" @@ -1097,9 +1048,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -1109,15 +1060,18 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] -name = "matchit" -version = "0.7.3" +name = "matchers" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] [[package]] name = "matchit" @@ -1195,66 +1149,55 @@ checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "netlink-packet-core" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +checksum = "745d789fe0958caf7252f5e1e900ce5c09b6a5bf05c7bba02a9cc600866ce31e" dependencies = [ - "anyhow", - "byteorder", - "netlink-packet-utils", + "pastey", ] [[package]] name = "netlink-packet-generic" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7eb8ad331c84c6b8cb7f685b448133e5ad82e1ffd5acafac374af4a5a308b" +checksum = "2f891b2e0054cac5a684a06628f59568f841c93da4e551239da6e518f539e775" dependencies = [ - "anyhow", - "byteorder", "netlink-packet-core", - "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" -version = "0.22.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0e7987b28514adf555dc1f9a5c30dfc3e50750bbaffb1aec41ca7b23dcd8e4" +checksum = "3ec2f5b6839be2a19d7fa5aab5bc444380f6311c2b693551cb80f45caaa7b5ef" dependencies = [ - "anyhow", "bitflags", - "byteorder", "libc", "log", "netlink-packet-core", - "netlink-packet-utils", ] [[package]] name = "netlink-packet-utils" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +checksum = "3176f18d11a1ae46053e59ec89d46ba318ae1343615bd3f8c908bfc84edae35c" dependencies = [ - "anyhow", "byteorder", - "paste", - "thiserror 1.0.69", + "pastey", + "thiserror", ] [[package]] name = "netlink-packet-wireguard" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b25b050ff1f6a1e23c6777b72db22790fe5b6b5ccfd3858672587a79876c8f" +checksum = "598962d9067d3153a00106da10e7b8276cea68f396f4a22f5b4a079270d92e29" dependencies = [ - "anyhow", - "byteorder", "libc", "log", + "netlink-packet-core", "netlink-packet-generic", - "netlink-packet-utils", ] [[package]] @@ -1301,6 +1244,15 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1344,16 +1296,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] -name = "paste" -version = "1.0.15" +name = "os_info" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" +dependencies = [ + "log", + "plist", + "serde", + "windows-sys 0.52.0", +] + +[[package]] +name = "pastey" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "petgraph" @@ -1362,7 +1326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.10.0", + "indexmap", ] [[package]] @@ -1403,6 +1367,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plist" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" +dependencies = [ + "base64", + "indexmap", + "quick-xml", + "serde", + "time", +] + [[package]] name = "portable-atomic" version = "1.11.1" @@ -1420,9 +1397,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] @@ -1433,20 +1410,11 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "prettyplease" -version = "0.2.35" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn", @@ -1454,18 +1422,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" dependencies = [ "bytes", "prost-derive", @@ -1473,9 +1441,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" dependencies = [ "heck", "itertools", @@ -1486,6 +1454,8 @@ dependencies = [ "prettyplease", "prost", "prost-types", + "pulldown-cmark", + "pulldown-cmark-to-cmark", "regex", "syn", "tempfile", @@ -1493,9 +1463,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", "itertools", @@ -1506,49 +1476,57 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" dependencies = [ "prost", ] [[package]] -name = "quote" -version = "1.0.40" +name = "pulldown-cmark" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" dependencies = [ - "proc-macro2", + "bitflags", + "memchr", + "unicase", ] [[package]] -name = "r-efi" -version = "5.3.0" +name = "pulldown-cmark-to-cmark" +version = "21.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "e5b6a0769a491a08b31ea5c62494a8f144ee0987d86d670a8af4df1e1b7cde75" +dependencies = [ + "pulldown-cmark", +] [[package]] -name = "rand" -version = "0.8.5" +name = "quick-xml" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" dependencies = [ - "libc", - "rand_chacha", - "rand_core", + "memchr", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "quote" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ - "ppv-lite86", - "rand_core", + "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand_core" version = "0.6.4" @@ -1560,9 +1538,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", @@ -1572,9 +1550,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", @@ -1583,9 +1561,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "ring" @@ -1603,9 +1581,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc_version" @@ -1618,22 +1596,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] name = "rustls" -version = "0.23.28" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "log", "once_cell", @@ -1656,15 +1634,6 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -1676,9 +1645,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -1687,9 +1656,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -1699,18 +1668,18 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] name = "security-framework" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" dependencies = [ "bitflags", "core-foundation", @@ -1721,9 +1690,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -1734,6 +1703,9 @@ name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] [[package]] name = "serde" @@ -1757,9 +1729,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -1779,9 +1751,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] @@ -1798,6 +1770,15 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1806,18 +1787,18 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -1827,12 +1808,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1855,9 +1836,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -1895,40 +1876,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] name = "thiserror" -version = "1.0.69" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -1936,24 +1908,21 @@ dependencies = [ ] [[package]] -name = "thiserror-impl" -version = "2.0.12" +name = "thread_local" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ - "proc-macro2", - "quote", - "syn", + "cfg-if", ] [[package]] name = "time" -version = "0.3.41" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" dependencies = [ "deranged", - "itoa", "libc", "num-conv", "num_threads", @@ -1965,15 +1934,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -1991,19 +1960,21 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2040,9 +2011,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -2053,47 +2024,43 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ "serde", ] [[package]] -name = "toml_edit" -version = "0.22.27" +name = "toml_parser" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" dependencies = [ - "indexmap 2.10.0", - "serde", - "serde_spanned", - "toml_datetime", "winnow", ] [[package]] name = "tonic" -version = "0.12.3" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" dependencies = [ - "async-stream", "async-trait", - "axum 0.7.9", + "axum", "base64", "bytes", "flate2", @@ -2106,14 +2073,13 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "prost", "rustls-native-certs", - "rustls-pemfile", "socket2", + "sync_wrapper", "tokio", "tokio-rustls", "tokio-stream", - "tower 0.4.13", + "tower", "tower-layer", "tower-service", "tracing", @@ -2121,36 +2087,41 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.12.3" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +checksum = "4c40aaccc9f9eccf2cd82ebc111adc13030d23e887244bc9cfa5d1d636049de3" dependencies = [ "prettyplease", "proc-macro2", - "prost-build", - "prost-types", "quote", "syn", ] [[package]] -name = "tower" -version = "0.4.13" +name = "tonic-prost" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" dependencies = [ - "futures-core", - "futures-util", - "indexmap 1.9.3", - "pin-project", - "pin-project-lite", - "rand", - "slab", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tonic-prost-build" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a16cba4043dc3ff43fcb3f96b4c5c154c64cbd18ca8dce2ab2c6a451d058a2" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn", + "tempfile", + "tonic-build", ] [[package]] @@ -2161,9 +2132,12 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", + "indexmap", "pin-project-lite", + "slab", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -2211,6 +2185,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -2219,11 +2223,17 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "untrusted" @@ -2233,13 +2243,14 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -2254,6 +2265,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2316,11 +2333,20 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.5+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" dependencies = [ - "wit-bindgen-rt", + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.0+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" +dependencies = [ + "wit-bindgen", ] [[package]] @@ -2329,6 +2355,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-sys" version = "0.52.0" @@ -2353,7 +2385,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", ] [[package]] @@ -2374,10 +2415,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -2486,21 +2528,15 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" -dependencies = [ - "memchr", -] +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" [[package]] name = "writeable" @@ -2544,26 +2580,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zerofrom" version = "0.1.6" @@ -2618,9 +2634,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", diff --git a/Cargo.toml b/Cargo.toml index 6ab6afd3..20fbf4e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,31 +1,38 @@ [package] name = "defguard-gateway" -version = "1.4.1" +version = "1.5.0" edition = "2021" [dependencies] -axum = { version = "0.8", features = ["macros"] } +defguard_version = { git = "https://github.com/DefGuard/defguard.git", rev = "c8e1bc91e0410a91af0b124d2d2b57c5a7226517" } +axum = "0.8" base64 = "0.22" clap = { version = "4.5", features = ["derive", "env"] } -defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.7.5" } +defguard_wireguard_rs = "0.7.6" env_logger = "0.11" gethostname = "1.0" ipnetwork = "0.21" libc = { version = "0.2", default-features = false } log = "0.4" -prost = "0.13" +prost = "0.14" serde = { version = "1.0", features = ["derive"] } syslog = "7.0" thiserror = "2.0" tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] } tokio-stream = { version = "0.1", features = [] } -toml = { version = "0.8", default-features = false, features = ["parse"] } -tonic = { version = "0.12", default-features = false, features = [ +toml = { version = "0.9", default-features = false, features = [ + "parse", + "serde", +] } +tonic = { version = "0.14", default-features = false, features = [ "codegen", "gzip", - "prost", "tls-native-roots", + "tls-ring", ] } +tracing = "0.1" +tonic-prost = "0.14" +tower = "0.5" [target.'cfg(target_os = "linux")'.dependencies] nftnl = { git = "https://github.com/DefGuard/nftnl-rs.git", rev = "1a1147271f43b9d7182a114bb056a5224c35d38f" } @@ -36,15 +43,15 @@ nix = { version = "0.30", default-features = false, features = ["ioctl"] } [dev-dependencies] tokio = { version = "1", features = ["io-std", "io-util"] } -tonic = { version = "0.12", default-features = false, features = [ +tonic = { version = "0.14", default-features = false, features = [ "codegen", - "prost", + "router", "transport", ] } x25519-dalek = { version = "2.0", features = ["getrandom", "static_secrets"] } [build-dependencies] -tonic-build = { version = "0.12" } +tonic-prost-build = "0.14" vergen-git2 = { version = "1.0", features = ["build"] } [profile.release] diff --git a/Dockerfile b/Dockerfile index 16ba3ccb..2c2d714c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -FROM rust:1-slim as builder +FROM rust:1-slim AS builder RUN apt-get update && apt-get -y install protobuf-compiler libnftnl-dev libmnl-dev WORKDIR /app COPY . . RUN cargo build --release -FROM debian:bookworm-slim +FROM debian:13-slim RUN apt-get update && apt-get -y --no-install-recommends install \ iproute2 wireguard-tools sudo ca-certificates iptables ebtables nftables && \ apt-get clean && rm -rf /var/lib/apt/lists/* diff --git a/README.md b/README.md index b7516f51..dfca4e14 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ If you already have your defguard instance running you can set up a gateway by f ## Documentation -See the [documentation](https://defguard.gitbook.io) for more information. +See the [documentation](https://docs.defguard.net) for more information. ## Community and Support @@ -22,5 +22,34 @@ Find us on Matrix: [#defguard:teonite.com](https://matrix.to/#/#defguard:teonite Please review the [Contributing guide](https://defguard.gitbook.io/defguard/for-developers/contributing) for information on how to get started contributing to the project. You might also find our [environment setup guide](https://defguard.gitbook.io/defguard/for-developers/dev-env-setup) handy. +## Verifiability of releases + +We provide following ways to verify the authenticity and integrity of official releases: + +### Docker Image Verification with Cosign + +All official Docker images are signed using [Cosign](https://docs.sigstore.dev/cosign/overview/). To verify a Docker image: + +1. [Install](https://github.com/sigstore/cosign?tab=readme-ov-file#installation) cosign CLI + +2. Verify the image signature (replace with the tag you want to verify): + ```bash + cosign verify --certificate-identity-regexp="https://github.com/DefGuard/gateway" \ + --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ + ghcr.io/defguard/defguard: + ``` + +### Release Asset Verification + +All release assets (binaries, packages, etc.) include SHA256 checksums that are automatically generated and published with each GitHub release: + +1. Download the release asset and copy its corresponding checksum from the [releases page](https://github.com/DefGuard/gateway/releases) + +2. Verify the checksum: + ```bash + # Linux/macOS + echo known_sha256_checksum_of_the_file path/to/file | sha256sum --check + ``` + # Legal WireGuard is [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld. diff --git a/build.rs b/build.rs index 74db20ad..e9fddc48 100644 --- a/build.rs +++ b/build.rs @@ -5,18 +5,17 @@ fn main() -> Result<(), Box> { let git2 = Git2Builder::default().branch(true).sha(true).build()?; Emitter::default().add_instructions(&git2)?.emit()?; - // compiling protos using path on build time - let mut config = tonic_build::Config::new(); - // enable optional fields - config.protoc_arg("--experimental_allow_proto3_optional"); - tonic_build::configure().compile_protos_with_config( - config, - &[ - "proto/wireguard/gateway.proto", - "proto/enterprise/firewall/firewall.proto", - ], - &["proto/wireguard", "proto/enterprise/firewall"], - )?; + tonic_prost_build::configure() + // enable optional fields + .protoc_arg("--experimental_allow_proto3_optional") + // compiling protos using path on build time + .compile_protos( + &[ + "proto/wireguard/gateway.proto", + "proto/enterprise/firewall/firewall.proto", + ], + &["proto/wireguard", "proto/enterprise/firewall"], + )?; println!("cargo:rerun-if-changed=proto"); Ok(()) } diff --git a/deny.toml b/deny.toml index 05ea3fa6..4c2d088a 100644 --- a/deny.toml +++ b/deny.toml @@ -69,9 +69,7 @@ feature-depth = 1 #db-urls = ["https://github.com/rustsec/advisory-db"] # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. -ignore = [ - { id = "RUSTSEC-2024-0436", reason = "Unmaintained" }, -] +ignore = [{ id = "RUSTSEC-2024-0436", reason = "Unmaintained" }] # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. @@ -92,7 +90,7 @@ allow = [ "MPL-2.0", "BSD-3-Clause", "Unicode-3.0", - "Unicode-DFS-2016", # unicode-ident + "Unicode-DFS-2016", # unicode-ident "Zlib", "ISC", "BSL-1.0", @@ -109,7 +107,8 @@ confidence-threshold = 0.8 # Allow 1 or more licenses on a per-crate basis, so that particular licenses # aren't accepted for every possible crate as with the normal allow list exceptions = [ - { allow = ["AGPL-3.0"], crate = "defguard-gateway" }, + { allow = ["AGPL-3.0-only"], crate = "defguard-gateway" }, + { allow = ["AGPL-3.0-only"], crate = "defguard_version" } ] # Some crates don't have (easily) machine readable licensing information, diff --git a/flake.lock b/flake.lock index 19484013..abf59ab7 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1746904237, - "narHash": "sha256-3e+AVBczosP5dCLQmMoMEogM57gmZ2qrVSrmq9aResQ=", + "lastModified": 1757347588, + "narHash": "sha256-tLdkkC6XnsY9EOZW9TlpesTclELy8W7lL2ClL+nma8o=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d89fc19e405cb2d55ce7cc114356846a0ee5e956", + "rev": "b599843bad24621dcaa5ab60dac98f9b0eb1cabe", "type": "github" }, "original": { @@ -48,11 +48,11 @@ ] }, "locked": { - "lastModified": 1747190175, - "narHash": "sha256-s33mQ2s5L/2nyllhRTywgECNZyCqyF4MJeM3vG/GaRo=", + "lastModified": 1757471515, + "narHash": "sha256-0+rSzNsYindDWjO9VVULKGjXlPsQV6IDjRU5G3SwI9U=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "58160be7abad81f6f8cb53120d5b88c16e01c06d", + "rev": "aecf31120156fe47a7d1992aa814052910178fca", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 08e0ab97..0d06e026 100644 --- a/flake.nix +++ b/flake.nix @@ -25,6 +25,7 @@ }; rustToolchain = pkgs.rust-bin.stable.latest.default.override { extensions = ["rust-analyzer" "rust-src" "rustfmt" "clippy"]; + targets = ["x86_64-unknown-linux-gnu" "armv7-unknown-linux-gnueabihf" "aarch64-unknown-linux-gnu" "x86_64-unknown-freebsd"]; }; in { devShells.default = pkgs.mkShell { diff --git a/images/ami/gateway.pkr.hcl b/images/ami/gateway.pkr.hcl new file mode 100644 index 00000000..3b3bd353 --- /dev/null +++ b/images/ami/gateway.pkr.hcl @@ -0,0 +1,62 @@ +packer { + required_plugins { + amazon = { + version = ">= 1.2.8" + source = "github.com/hashicorp/amazon" + } + } +} + +variable "package_version" { + type = string +} + +variable "region" { + type = string + default = "eu-north-1" +} + +variable "instance_type" { + type = string + default = "t3.micro" +} + +source "amazon-ebs" "defguard-gateway" { + ami_name = "defguard-gateway-${var.package_version}-amd64" + instance_type = var.instance_type + region = var.region + source_ami_filter { + filters = { + name = "debian-13-amd64-*" + root-device-type = "ebs" + virtualization-type = "hvm" + } + most_recent = true + owners = ["136693071363"] + } + ssh_username = "admin" +} + +build { + name = "defguard-gateway" + sources = [ + "source.amazon-ebs.defguard-gateway" + ] + + provisioner "file" { + source = "defguard-gateway_${var.package_version}_x86_64-unknown-linux-gnu.deb" + destination = "/tmp/defguard-gateway.deb" + } + + provisioner "shell" { + script = "./images/ami/gateway.sh" + } + + provisioner "shell" { + inline = ["rm /home/admin/.ssh/authorized_keys"] + } + + provisioner "shell" { + inline = ["sudo rm /root/.ssh/authorized_keys"] + } +} diff --git a/images/ami/gateway.sh b/images/ami/gateway.sh new file mode 100644 index 00000000..c0b769c9 --- /dev/null +++ b/images/ami/gateway.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e + +echo "Updating apt repositories..." +sudo apt update + +echo "Installing Defguard Gateway package..." +sudo dpkg -i /tmp/defguard-gateway.deb + +echo "Cleaning up..." +sudo rm -f /tmp/defguard-gateway.deb + +echo "Defguard Gateway installation completed successfully." diff --git a/proto b/proto index d72ced89..883487df 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit d72ced898411c4b8144bb13a9ad48f65e2f6a1ec +Subproject commit 883487df67d90fd14fae900737cd8b5ea6c10de3 diff --git a/src/config.rs b/src/config.rs index c8af948c..00445ffa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use std::{fs, path::PathBuf}; +use std::{fs, net::IpAddr, path::PathBuf}; use clap::Parser; use serde::Deserialize; @@ -6,10 +6,22 @@ use toml; use crate::error::GatewayError; +fn default_log_level() -> String { + String::from("info") +} + +fn default_syslog_socket() -> PathBuf { + PathBuf::from("/var/run/log") +} + #[derive(Debug, Parser, Clone, Deserialize)] #[clap(about = "Defguard VPN gateway service")] #[command(version)] pub struct Config { + #[arg(long, short = 'l', env = "DEFGUARD_LOG_LEVEL", default_value = "info")] + #[serde(default = "default_log_level")] + pub log_level: String, + /// Token received from Defguard after completing the network wizard #[arg( long, @@ -18,6 +30,7 @@ pub struct Config { env = "DEFGUARD_TOKEN", default_value = "" )] + #[serde(default)] pub token: String, #[arg(long, env = "DEFGUARD_GATEWAY_NAME")] @@ -31,6 +44,7 @@ pub struct Config { env = "DEFGUARD_GRPC_URL", default_value = "" )] + #[serde(default)] pub grpc_url: String, /// Use userspace WireGuard implementation e.g. wireguard-go @@ -63,6 +77,7 @@ pub struct Config { /// Syslog socket path #[arg(long, default_value = "/var/run/log")] + #[serde(default = "default_syslog_socket")] pub syslog_socket: PathBuf, /// Configuration file path @@ -105,11 +120,15 @@ pub struct Config { #[arg(long, env = "DEFGUARD_DISABLE_FW_MGMT")] #[serde(default)] pub disable_firewall_management: bool, + + #[arg(long, env = "DEFGUARD_HTTP_BIND_ADDRESS")] + pub http_bind_address: Option, } impl Default for Config { fn default() -> Self { Self { + log_level: "info".into(), token: "TOKEN".into(), name: None, grpc_url: "http://localhost:50051".into(), @@ -130,6 +149,7 @@ impl Default for Config { masquerade: false, fw_priority: None, disable_firewall_management: false, + http_bind_address: None, } } } @@ -142,7 +162,7 @@ pub fn get_config() -> Result { if let Some(config_path) = cli_config.config_path { let config_toml = fs::read_to_string(config_path) .map_err(|err| GatewayError::InvalidConfigFile(err.to_string()))?; - let file_config: Config = toml::from_str(&config_toml) + let file_config = toml::from_str(&config_toml) .map_err(|err| GatewayError::InvalidConfigFile(err.message().to_string()))?; return Ok(file_config); } diff --git a/src/enterprise/firewall/api.rs b/src/enterprise/firewall/api.rs index 67960927..9fb4c4af 100644 --- a/src/enterprise/firewall/api.rs +++ b/src/enterprise/firewall/api.rs @@ -4,7 +4,7 @@ use std::fs::{File, OpenOptions}; #[cfg(target_os = "linux")] use nftnl::Batch; -use super::{FirewallError, FirewallRule, Policy}; +use super::{FirewallError, FirewallRule, Policy, SnatBinding}; #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] const DEV_PF: &str = "/dev/pf"; @@ -42,11 +42,15 @@ pub(crate) trait FirewallManagementApi { /// Clean up the firewall rules. fn cleanup(&mut self) -> Result<(), FirewallError>; - /// Add fireall `rules`. + /// Add firewall rules. fn add_rules(&mut self, rules: Vec) -> Result<(), FirewallError>; - /// Set masquerade status. - fn set_masquerade_status(&mut self, enabled: bool) -> Result<(), FirewallError>; + /// Setup Network Address Translation using POSTROUTING chain rules + fn setup_nat( + &mut self, + masquerade_enabled: bool, + snat_bindings: &[SnatBinding], + ) -> Result<(), FirewallError>; /// Begin rule transaction. fn begin(&mut self) -> Result<(), FirewallError>; diff --git a/src/enterprise/firewall/dummy/mod.rs b/src/enterprise/firewall/dummy/mod.rs deleted file mode 100644 index 9129f91f..00000000 --- a/src/enterprise/firewall/dummy/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -use super::{ - api::{FirewallApi, FirewallManagementApi}, - FirewallError, FirewallRule, Policy, -}; - -impl FirewallManagementApi for FirewallApi { - fn setup( - &mut self, - _default_policy: Policy, - _priority: Option, - ) -> Result<(), FirewallError> { - Ok(()) - } - - fn cleanup(&mut self) -> Result<(), FirewallError> { - Ok(()) - } - - fn set_masquerade_status(&mut self, _enabled: bool) -> Result<(), FirewallError> { - Ok(()) - } - - fn add_rules(&mut self, _rules: Vec) -> Result<(), FirewallError> { - Ok(()) - } - - fn begin(&mut self) -> Result<(), FirewallError> { - Ok(()) - } - - fn commit(&mut self) -> Result<(), FirewallError> { - Ok(()) - } -} diff --git a/src/enterprise/firewall/mod.rs b/src/enterprise/firewall/mod.rs index ef4304d2..e078af43 100644 --- a/src/enterprise/firewall/mod.rs +++ b/src/enterprise/firewall/mod.rs @@ -1,8 +1,6 @@ pub mod api; -#[cfg(test)] -mod dummy; mod iprange; -#[cfg(all(not(test), target_os = "linux"))] +#[cfg(target_os = "linux")] mod nftables; #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] mod packetfilter; @@ -222,10 +220,19 @@ pub(crate) struct FirewallRule { pub ipv4: bool, // FIXME: is that really needed? } +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct SnatBinding { + pub id: i64, + pub source_addrs: Vec
, + pub public_ip: IpAddr, + pub comment: Option, +} + #[derive(Debug, Clone, PartialEq)] pub(crate) struct FirewallConfig { pub rules: Vec, pub default_policy: Policy, + pub snat_bindings: Vec, } impl FirewallConfig { @@ -234,6 +241,7 @@ impl FirewallConfig { ) -> Result { debug!("Parsing following received firewall proto configuration: {config:?}"); let mut rules = Vec::new(); + let mut snat_bindings = Vec::new(); let default_policy = Policy::from_proto(config.default_policy.try_into().map_err(|err| { FirewallError::TypeConversionError(format!("Invalid default policy: {err:?}")) @@ -242,6 +250,7 @@ impl FirewallConfig { "Default firewall policy defined: {default_policy:?}. Proceeding to parsing rules..." ); + // parse received firewall rules for rule in config.rules { debug!("Parsing the following received Defguard ACL proto rule: {rule:?}"); let mut source_addrs = Vec::new(); @@ -305,9 +314,37 @@ impl FirewallConfig { rules.push(firewall_rule); } + // parse received SNAT bindings + for binding in config.snat_bindings { + debug!("Parsing the following received SNAT binding proto: {binding:?}"); + + let mut source_addrs = Vec::new(); + for addr in binding.source_addrs { + source_addrs.push(Address::from_proto(&addr)?); + } + + let public_ip = binding.public_ip.parse().map_err(|err| { + FirewallError::TypeConversionError(format!( + "Invalid public IP address format: {err}" + )) + })?; + + let snat_binding = SnatBinding { + id: binding.id, + source_addrs, + public_ip, + comment: binding.comment, + }; + + debug!("Parsed received proto SNAT binding as: {snat_binding:?}"); + + snat_bindings.push(snat_binding); + } + Ok(Self { rules, default_policy, + snat_bindings, }) } } @@ -345,6 +382,7 @@ pub enum FirewallError { /// /// - In IPv4 this is the broadcast address. /// - In IPv6 this is just the last address in the network. +#[must_use] pub fn max_address(network: &IpNetwork) -> IpAddr { match network { IpNetwork::V4(network) => { diff --git a/src/enterprise/firewall/nftables/mod.rs b/src/enterprise/firewall/nftables/mod.rs index 614b9831..94af5e00 100644 --- a/src/enterprise/firewall/nftables/mod.rs +++ b/src/enterprise/firewall/nftables/mod.rs @@ -7,14 +7,14 @@ use std::{ use netfilter::{ allow_established_traffic, apply_filter_rules, drop_table, ignore_unrelated_traffic, - init_firewall, send_batch, set_masq, + init_firewall, send_batch, set_nat_rules, }; use nftnl::Batch; use super::{ api::{FirewallApi, FirewallManagementApi}, iprange::IpAddrRangeError, - Address, FirewallError, FirewallRule, Policy, Port, Protocol, + Address, FirewallError, FirewallRule, Policy, Port, Protocol, SnatBinding, }; use crate::enterprise::firewall::iprange::IpAddrRange; @@ -268,27 +268,20 @@ impl FirewallManagementApi for FirewallApi { Ok(()) } - // Allows for changing the default policy of the firewall. - // fn set_firewall_default_policy(&mut self, policy: Policy) -> Result<(), FirewallError> { - // debug!("Setting default firewall policy to: {policy:?}"); - // if let Some(batch) = &mut self.batch { - // set_default_policy(policy, batch, &self.ifname)?; - // } else { - // return Err(FirewallError::TransactionNotStarted); - // } - // debug!("Set firewall default policy to {policy:?}"); - // Ok(()) - // } - - /// Allows for changing the masquerade status of the firewall. - fn set_masquerade_status(&mut self, enabled: bool) -> Result<(), FirewallError> { - debug!("Setting masquerade status to: {enabled:?}"); + fn setup_nat( + &mut self, + masquerade_enabled: bool, + snat_bindings: &[SnatBinding], + ) -> Result<(), FirewallError> { + debug!("Setting up POSTROUTING chain rules with masquerade status: {masquerade_enabled} and SNAT bindings: {snat_bindings:?}"); + if let Some(batch) = &mut self.batch { - set_masq(&self.ifname, enabled, batch)?; + set_nat_rules(batch, &self.ifname, masquerade_enabled, snat_bindings)?; } else { return Err(FirewallError::TransactionNotStarted); } - debug!("Set masquerade status to: {enabled:?}"); + + debug!("Finished POSTROUTING chain rules setup"); Ok(()) } diff --git a/src/enterprise/firewall/nftables/netfilter.rs b/src/enterprise/firewall/nftables/netfilter.rs index b5d8505c..06e2d62d 100644 --- a/src/enterprise/firewall/nftables/netfilter.rs +++ b/src/enterprise/firewall/nftables/netfilter.rs @@ -4,14 +4,14 @@ use std::{ }; use nftnl::{ - expr::{Expression, InterfaceName}, + expr::{Expression, Immediate, InterfaceName, Nat, NatType, Register}, nft_expr, nftnl_sys, set::{Set, SetKey}, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table, }; use super::{get_set_id, Address, FilterRule, Policy, Port, Protocol, State}; -use crate::enterprise::firewall::{iprange::IpAddrRange, max_address, FirewallError}; +use crate::enterprise::firewall::{iprange::IpAddrRange, max_address, FirewallError, SnatBinding}; const FILTER_TABLE: &str = "filter"; const NAT_TABLE: &str = "nat"; @@ -143,6 +143,26 @@ fn add_protocol_to_set( Ok(()) } +fn add_rule_comment(rule: &mut Rule, comment: &str) -> Result<(), FirewallError> { + debug!("Adding comment to nftables expression: {comment:?}"); + // Since we are interoping with C, truncate the string to 255 *bytes* (not UTF-8 characters) + // 256 is the maximum length of a comment string in nftables, leave 1 byte for the null terminator + let maybe_truncated_str = if comment.len() > 255 { + warn!("Comment string {comment} is too long, truncating to 255 bytes"); + &comment[..=255] + } else { + comment + }; + let comment = &CString::new(maybe_truncated_str).map_err(|e| { + FirewallError::NetlinkError(format!( + "Failed to create CString from string {comment}. Error: {e:?}" + )) + })?; + rule.set_comment(comment); + debug!("Added comment to nftables expression: {comment:?}"); + Ok(()) +} + impl FirewallRule for FilterRule<'_> { fn to_chain_rule<'a>( &self, @@ -403,22 +423,7 @@ impl FirewallRule for FilterRule<'_> { // comment if let Some(comment_string) = &self.comment { - debug!("Adding comment to nftables expression: {comment_string:?}"); - // Since we are interoping with C, truncate the string to 255 *bytes* (not UTF-8 characters) - // 256 is the maximum length of a comment string in nftables, leave 1 byte for the null terminator - let maybe_truncated_str = if comment_string.len() > 255 { - warn!("Comment string {comment_string} is too long, truncating to 255 bytes"); - &comment_string[..=255] - } else { - comment_string.as_str() - }; - let comment = &CString::new(maybe_truncated_str).map_err(|e| { - FirewallError::NetlinkError(format!( - "Failed to create CString from string {comment_string}. Error: {e:?}" - )) - })?; - rule.set_comment(comment); - debug!("Added comment to nftables expression: {comment_string:?}"); + add_rule_comment(&mut rule, comment_string)?; } else { debug!("No comment provided for nftables expression"); } @@ -430,18 +435,14 @@ impl FirewallRule for FilterRule<'_> { } } -#[derive(Debug, Default)] -struct NatRule { - src_ip: Option, - dest_ip: Option, - oifname: Option, - iifname: Option, +#[derive(Debug)] +struct MasqueradeRule { + oifname: String, negated_oifname: bool, - negated_iifname: bool, counter: bool, } -impl FirewallRule for NatRule { +impl FirewallRule for MasqueradeRule { fn to_chain_rule<'a>( &self, chain: &'a Chain, @@ -449,49 +450,125 @@ impl FirewallRule for NatRule { ) -> Result, FirewallError> { let mut rule = Rule::new(chain); - if let Some(src_ip) = self.src_ip { - if src_ip.is_ipv4() { - rule.add_expr(&nft_expr!(payload ipv4 saddr)); - } else { - rule.add_expr(&nft_expr!(payload ipv6 saddr)); - } - rule.add_expr(&nft_expr!(cmp == src_ip)); + rule.add_expr(&nft_expr!(meta oifname)); + let exact = InterfaceName::Exact(CString::new(self.oifname.as_str()).unwrap()); + if self.negated_oifname { + rule.add_expr(&nft_expr!(cmp != exact)); + } else { + rule.add_expr(&nft_expr!(cmp == exact)); } - if let Some(dest_ip) = self.dest_ip { - if dest_ip.is_ipv4() { - rule.add_expr(&nft_expr!(payload ipv4 daddr)); - } else { - rule.add_expr(&nft_expr!(payload ipv6 daddr)); - } - rule.add_expr(&nft_expr!(cmp == dest_ip)); + if self.counter { + rule.add_expr(&nft_expr!(counter)); } - if let Some(iifname) = &self.iifname { - rule.add_expr(&nft_expr!(meta iifname)); - let exact = InterfaceName::Exact(CString::new(iifname.as_str()).unwrap()); - if self.negated_iifname { - rule.add_expr(&nft_expr!(cmp != exact)); + rule.add_expr(&nft_expr!(masquerade)); + + Ok(rule) + } +} + +#[derive(Debug)] +struct SnatRule<'a> { + src_ips: &'a [Address], + public_ip: &'a IpAddr, + oifname: String, + negated_oifname: bool, + counter: bool, + ipv4: bool, + comment: Option, +} + +impl FirewallRule for SnatRule<'_> { + fn to_chain_rule<'a>( + &self, + chain: &'a Chain, + batch: &mut Batch, + ) -> Result, FirewallError> { + let mut rule = Rule::new(chain); + + if !self.src_ips.is_empty() { + if self.ipv4 { + let set = new_anon_set::(chain.get_table(), ProtoFamily::Inet, true)?; + batch.add(&set, nftnl::MsgType::Add); + + for ip in self.src_ips { + add_address_to_set(set.as_ptr(), ip)?; + } + + // ip saddr {x.x.x.x, x.x.x.x} + set.elems_iter().for_each(|elem| { + batch.add(&elem, nftnl::MsgType::Add); + }); + + rule.add_expr(&nft_expr!(meta nfproto)); + rule.add_expr(&nft_expr!(cmp == libc::NFPROTO_IPV4 as u8)); + rule.add_expr(&nft_expr!(payload ipv4 saddr)); + + rule.add_expr(&nft_expr!(lookup & set)); } else { - rule.add_expr(&nft_expr!(cmp == exact)); + let set = new_anon_set::(chain.get_table(), ProtoFamily::Inet, true)?; + batch.add(&set, nftnl::MsgType::Add); + + for ip in self.src_ips { + add_address_to_set(set.as_ptr(), ip)?; + } + + // ip6 saddr {x.x.x.x, x.x.x.x} + set.elems_iter().for_each(|elem| { + batch.add(&elem, nftnl::MsgType::Add); + }); + + rule.add_expr(&nft_expr!(meta nfproto)); + rule.add_expr(&nft_expr!(cmp == libc::NFPROTO_IPV6 as u8)); + rule.add_expr(&nft_expr!(payload ipv6 saddr)); + + rule.add_expr(&nft_expr!(lookup & set)); } + debug!( + "Added source IP addresses match to nftables expression: {:?}", + self.src_ips + ); } - if let Some(oifname) = &self.oifname { - rule.add_expr(&nft_expr!(meta oifname)); - let exact = InterfaceName::Exact(CString::new(oifname.as_str()).unwrap()); - if self.negated_oifname { - rule.add_expr(&nft_expr!(cmp != exact)); - } else { - rule.add_expr(&nft_expr!(cmp == exact)); - } + rule.add_expr(&nft_expr!(meta oifname)); + let exact = InterfaceName::Exact(CString::new(self.oifname.as_str()).unwrap()); + if self.negated_oifname { + rule.add_expr(&nft_expr!(cmp != exact)); + } else { + rule.add_expr(&nft_expr!(cmp == exact)); } if self.counter { rule.add_expr(&nft_expr!(counter)); } - rule.add_expr(&nft_expr!(masquerade)); + // determine if public IP is IPv4 or IPv6 and store the address in a register + let family = match self.public_ip { + IpAddr::V4(ipv4_addr) => { + rule.add_expr(&Immediate::new(*ipv4_addr, Register::Reg1)); + ProtoFamily::Ipv4 + } + IpAddr::V6(ipv6_addr) => { + rule.add_expr(&Immediate::new(*ipv6_addr, Register::Reg1)); + ProtoFamily::Ipv6 + } + }; + let snat_expr = Nat { + nat_type: NatType::SNat, + family, + ip_register: Register::Reg1, + port_register: None, + }; + + rule.add_expr(&snat_expr); + + // comment + if let Some(comment_string) = &self.comment { + add_rule_comment(&mut rule, comment_string)?; + } else { + debug!("No comment provided for nftables expression"); + } Ok(rule) } @@ -586,35 +663,54 @@ pub(super) fn drop_chain( Ok(()) } -/// Applies masquerade on the specified interface for the outgoing packets -pub(super) fn set_masq( - ifname: &str, - enabled: bool, +/// Applies NAT rules on the specified interface for the outgoing packets +pub(super) fn set_nat_rules( batch: &mut Batch, + ifname: &str, + masquerade_enabled: bool, + snat_bindings: &[SnatBinding], ) -> Result<(), FirewallError> { + // cleanup existing POSTROUTING chain rules let table = Tables::Defguard(ProtoFamily::Inet).to_table(ifname); batch.add(&table, nftnl::MsgType::Add); drop_chain(&Chains::Postrouting, batch, ifname)?; + // initialize new POSTROUTING chain let mut nat_chain = Chains::Postrouting.to_chain(&table); nat_chain.set_hook(nftnl::Hook::PostRouting, POSTROUTING_PRIORITY); nat_chain.set_policy(nftnl::Policy::Accept); nat_chain.set_type(nftnl::ChainType::Nat); batch.add(&nat_chain, nftnl::MsgType::Add); - let nat_rule = NatRule { - oifname: Some(LOOPBACK_IFACE.to_string()), + // add SNAT bindings + for binding in snat_bindings { + let snat_rule = SnatRule { + oifname: LOOPBACK_IFACE.to_string(), + negated_oifname: true, + counter: true, + src_ips: &binding.source_addrs, + public_ip: &binding.public_ip, + ipv4: binding.public_ip.is_ipv4(), + comment: binding.comment.clone(), + } + .to_chain_rule(&nat_chain, batch)?; + + batch.add(&snat_rule, nftnl::MsgType::Add); + } + + // add MASQUERADE rule + let masquerade_rule = MasqueradeRule { + oifname: LOOPBACK_IFACE.to_string(), negated_oifname: true, counter: true, - ..Default::default() } .to_chain_rule(&nat_chain, batch)?; - if enabled { - batch.add(&nat_rule, nftnl::MsgType::Add); + if masquerade_enabled { + batch.add(&masquerade_rule, nftnl::MsgType::Add); } else { - batch.add(&nat_rule, nftnl::MsgType::Del); + batch.add(&masquerade_rule, nftnl::MsgType::Del); } Ok(()) @@ -808,7 +904,7 @@ fn new_anon_set( table: &Table, family: ProtoFamily, interval_set: bool, -) -> Result, FirewallError> +) -> Result, FirewallError> where T: SetKey, { diff --git a/src/enterprise/firewall/packetfilter/api.rs b/src/enterprise/firewall/packetfilter/api.rs index baad67cd..ffa26c25 100644 --- a/src/enterprise/firewall/packetfilter/api.rs +++ b/src/enterprise/firewall/packetfilter/api.rs @@ -7,7 +7,7 @@ use super::{ }; use crate::enterprise::firewall::{ api::{FirewallApi, FirewallManagementApi}, - FirewallError, Policy, + FirewallError, Policy, SnatBinding, }; impl FirewallManagementApi for FirewallApi { @@ -34,7 +34,7 @@ impl FirewallManagementApi for FirewallApi { let mut ioc_trans = IocTrans::new(elements.as_mut_slice()); // This will create an anchor if it doesn't exist. unsafe { - pf_begin(self.fd(), &mut ioc_trans)?; + pf_begin(self.fd(), &raw mut ioc_trans)?; } let ticket = elements[0].ticket; @@ -46,7 +46,7 @@ impl FirewallManagementApi for FirewallApi { debug!("Rollback pf transaction."); // Rule cannot be added, so rollback. unsafe { - pf_rollback(self.fd(), &mut ioc_trans)?; + pf_rollback(self.fd(), &raw mut ioc_trans)?; return Err(FirewallError::TransactionFailed(err.to_string())); } } @@ -57,7 +57,7 @@ impl FirewallManagementApi for FirewallApi { debug!("Rollback pf transaction."); // Rule cannot be added, so rollback. unsafe { - pf_rollback(self.fd(), &mut ioc_trans)?; + pf_rollback(self.fd(), &raw mut ioc_trans)?; return Err(FirewallError::TransactionFailed(err.to_string())); } } @@ -66,14 +66,18 @@ impl FirewallManagementApi for FirewallApi { // Commit transaction. debug!("Commit pf transaction."); unsafe { - pf_commit(self.file.as_raw_fd(), &mut ioc_trans).unwrap(); + pf_commit(self.file.as_raw_fd(), &raw mut ioc_trans).unwrap(); } Ok(()) } - /// Set masquerade status. - fn set_masquerade_status(&mut self, _enabled: bool) -> Result<(), FirewallError> { + /// Setup Network Address Translation using POSTROUTING chain rules + fn setup_nat( + &mut self, + _masquerade_enabled: bool, + _snat_bindings: &[SnatBinding], + ) -> Result<(), FirewallError> { Ok(()) } diff --git a/src/enterprise/firewall/packetfilter/calls.rs b/src/enterprise/firewall/packetfilter/calls.rs index e81b7312..e4f2054e 100644 --- a/src/enterprise/firewall/packetfilter/calls.rs +++ b/src/enterprise/firewall/packetfilter/calls.rs @@ -115,14 +115,14 @@ impl AddrWrap { #[must_use] fn with_interface(ifname: &str) -> Self { let mut uninit = MaybeUninit::::zeroed(); - let self_ptr = uninit.as_mut_ptr(); let len = ifname.len().min(IFNAMSIZ - 1); unsafe { - (*self_ptr).v.ifname[..len].copy_from_slice(&ifname.as_bytes()[..len]); + let self_ptr = &mut *uninit.as_mut_ptr(); + self_ptr.v.ifname[..len].copy_from_slice(&ifname.as_bytes()[..len]); // Probably, this is needed only for pfctl to omit displaying number of bits. // FIXME: Fill all bytes for IPv6. - (*self_ptr).v.a.mask[..4].fill(255); - (*self_ptr).r#type = AddrType::DynIftl; + self_ptr.v.a.mask[..4].fill(255); + self_ptr.r#type = AddrType::DynIftl; } unsafe { uninit.assume_init() } @@ -211,7 +211,7 @@ struct TailQueue { impl TailQueue { fn init(&mut self) { self.tqh_first = ptr::null_mut(); - self.tqh_last = &mut self.tqh_first; + self.tqh_last = &raw mut self.tqh_first; } } @@ -332,13 +332,14 @@ impl Pool { /// Insert `PoolAddr` at the end of the list. Take ownership of the given `PoolAddr`. pub(super) fn insert_pool_addr(&mut self, mut pool_addr: PoolAddr) { // TODO: Traverse tail queue; for now assume empty tail queue. - if !self.list.tqh_first.is_null() { - panic!("Expected one entry in PoolAddr TailQueue."); - } - self.list.tqh_first = &mut pool_addr; - self.list.tqh_last = &mut pool_addr.entries.tqe_next; + assert!( + self.list.tqh_first.is_null(), + "Expected one entry in PoolAddr TailQueue." + ); + self.list.tqh_first = &raw mut pool_addr; + self.list.tqh_last = &raw mut pool_addr.entries.tqe_next; pool_addr.entries.tqe_next = ptr::null_mut(); - pool_addr.entries.tqe_prev = &mut self.list.tqh_first; + pool_addr.entries.tqe_prev = &raw mut self.list.tqh_first; } } @@ -554,51 +555,52 @@ pub(super) struct Rule { impl Rule { pub(super) fn from_pf_rule(pf_rule: &PacketFilterRule) -> Self { let mut uninit = MaybeUninit::::zeroed(); - let self_ptr = uninit.as_mut_ptr(); unsafe { + let self_ptr = &mut *uninit.as_mut_ptr(); + if let Some(from) = pf_rule.from { - (*self_ptr).src = RuleAddr::new(from, pf_rule.from_port); + self_ptr.src = RuleAddr::new(from, pf_rule.from_port); } if let Some(to) = pf_rule.to { - (*self_ptr).dst = RuleAddr::new(to, pf_rule.to_port); + self_ptr.dst = RuleAddr::new(to, pf_rule.to_port); } if let Some(interface) = &pf_rule.interface { let len = interface.len().min(IFNAMSIZ - 1); - (*self_ptr).ifname[..len].copy_from_slice(&interface.as_bytes()[..len]); + self_ptr.ifname[..len].copy_from_slice(&interface.as_bytes()[..len]); } if let Some(label) = &pf_rule.label { let len = label.len().min(PF_RULE_LABEL_SIZE - 1); - (*self_ptr).label[..len].copy_from_slice(&label.as_bytes()[..len]); + self_ptr.label[..len].copy_from_slice(&label.as_bytes()[..len]); } // Don't use routing tables. #[cfg(any(target_os = "freebsd", target_os = "netbsd"))] { - (*self_ptr).rtableid = -1; + self_ptr.rtableid = -1; } #[cfg(target_os = "macos")] { - (*self_ptr).rtableid = 0; + self_ptr.rtableid = 0; } - (*self_ptr).action = pf_rule.action; - (*self_ptr).direction = pf_rule.direction; - (*self_ptr).log = pf_rule.log; - (*self_ptr).quick = pf_rule.quick; + self_ptr.action = pf_rule.action; + self_ptr.direction = pf_rule.direction; + self_ptr.log = pf_rule.log; + self_ptr.quick = pf_rule.quick; - (*self_ptr).keep_state = pf_rule.state; + self_ptr.keep_state = pf_rule.state; let af = pf_rule.address_family(); - (*self_ptr).af = af; + self_ptr.af = af; #[cfg(target_os = "macos")] { - (*self_ptr).rpool.af = af; + self_ptr.rpool.af = af; } - (*self_ptr).proto = pf_rule.proto as u8; - (*self_ptr).flags = pf_rule.tcp_flags; - (*self_ptr).flagset = pf_rule.tcp_flags_set; + self_ptr.proto = pf_rule.proto as u8; + self_ptr.flags = pf_rule.tcp_flags; + self_ptr.flagset = pf_rule.tcp_flags_set; - (*self_ptr).rpool.list.init(); + self_ptr.rpool.list.init(); uninit.assume_init() } @@ -658,13 +660,13 @@ impl IocRule { #[must_use] pub(super) fn with_rule(anchor: &str, rule: Rule) -> Self { let mut uninit = MaybeUninit::::zeroed(); - let self_ptr = uninit.as_mut_ptr(); // Copy anchor name. let len = anchor.len().min(MAXPATHLEN - 1); unsafe { - (*self_ptr).anchor[..len].copy_from_slice(&anchor.as_bytes()[..len]); - (*self_ptr).rule = rule; + let self_ptr = &mut *uninit.as_mut_ptr(); + self_ptr.anchor[..len].copy_from_slice(&anchor.as_bytes()[..len]); + self_ptr.rule = rule; } unsafe { uninit.assume_init() } @@ -689,12 +691,12 @@ impl IocPoolAddr { #[must_use] pub(super) fn new(anchor: &str) -> Self { let mut uninit = MaybeUninit::::zeroed(); - let self_ptr = uninit.as_mut_ptr(); // Copy anchor name. let len = anchor.len().min(MAXPATHLEN - 1); unsafe { - (*self_ptr).anchor[..len].copy_from_slice(&anchor.as_bytes()[..len]); + let self_ptr = &mut *uninit.as_mut_ptr(); + self_ptr.anchor[..len].copy_from_slice(&anchor.as_bytes()[..len]); } unsafe { uninit.assume_init() } @@ -704,10 +706,10 @@ impl IocPoolAddr { #[must_use] pub(super) fn with_pool_addr(addr: PoolAddr, ticket: c_uint) -> Self { let mut uninit = MaybeUninit::::zeroed(); - let self_ptr = uninit.as_mut_ptr(); unsafe { - (*self_ptr).ticket = ticket; - (*self_ptr).addr = addr; + let self_ptr = &mut *uninit.as_mut_ptr(); + self_ptr.ticket = ticket; + self_ptr.addr = addr; } unsafe { uninit.assume_init() } @@ -726,13 +728,13 @@ impl IocTransElement { #[must_use] pub(super) fn new(ruleset: RuleSet, anchor: &str) -> Self { let mut uninit = MaybeUninit::::zeroed(); - let self_ptr = uninit.as_mut_ptr(); // Copy anchor name. let len = anchor.len().min(MAXPATHLEN - 1); unsafe { - (*self_ptr).rs_num = ruleset; - (*self_ptr).anchor[..len].copy_from_slice(&anchor.as_bytes()[..len]); + let self_ptr = &mut *uninit.as_mut_ptr(); + self_ptr.rs_num = ruleset; + self_ptr.anchor[..len].copy_from_slice(&anchor.as_bytes()[..len]); } unsafe { uninit.assume_init() } diff --git a/src/enterprise/firewall/packetfilter/mod.rs b/src/enterprise/firewall/packetfilter/mod.rs index 81cccdce..ad439b13 100644 --- a/src/enterprise/firewall/packetfilter/mod.rs +++ b/src/enterprise/firewall/packetfilter/mod.rs @@ -42,7 +42,7 @@ impl FirewallApi { let mut ioc = IocPoolAddr::new(anchor); unsafe { - pf_begin_addrs(self.fd(), &mut ioc)?; + pf_begin_addrs(self.fd(), &raw mut ioc)?; } Ok(ioc.ticket) @@ -58,7 +58,7 @@ impl FirewallApi { let mut ioc = IocRule::with_rule(anchor, Rule::from_pf_rule(&rule)); ioc.ticket = ticket; ioc.pool_ticket = pool_ticket; - if let Err(err) = unsafe { pf_add_rule(self.fd(), &mut ioc) } { + if let Err(err) = unsafe { pf_add_rule(self.fd(), &raw mut ioc) } { error!("Packet filter rule {rule} can't be added."); return Err(err.into()); } @@ -82,7 +82,7 @@ impl FirewallApi { ioc.action = Change::None; ioc.ticket = ticket; ioc.pool_ticket = pool_ticket; - if let Err(err) = unsafe { pf_add_rule(self.fd(), &mut ioc) } { + if let Err(err) = unsafe { pf_add_rule(self.fd(), &raw mut ioc) } { error!("Packet filter rule {rule} can't be added."); return Err(err.into()); } @@ -92,5 +92,4 @@ impl FirewallApi { } } -#[cfg(not(test))] mod api; diff --git a/src/error.rs b/src/error.rs index d05e5d45..aceb3c4f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,4 @@ +use defguard_version::{DefguardVersionError, SemverError}; use defguard_wireguard_rs::error::WireguardInterfaceError; use thiserror::Error; @@ -43,4 +44,10 @@ pub enum GatewayError { #[error("Firewall error: {0}")] FirewallError(#[from] FirewallError), + + #[error(transparent)] + DefguardVersionError(#[from] DefguardVersionError), + + #[error(transparent)] + SemverError(#[from] SemverError), } diff --git a/src/gateway.rs b/src/gateway.rs index 039e3e9e..ce093adc 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -1,3 +1,9 @@ +use defguard_version::{ + client::ClientVersionInterceptor, get_tracing_variables, ComponentInfo, DefguardComponent, + Version, +}; +use defguard_wireguard_rs::{net::IpAddrMask, WireguardInterfaceApi}; +use gethostname::gethostname; use std::{ collections::HashMap, fs::read_to_string, @@ -8,9 +14,6 @@ use std::{ }, time::{Duration, SystemTime}, }; - -use defguard_wireguard_rs::{net::IpAddrMask, WireguardInterfaceApi}; -use gethostname::gethostname; use tokio::{ select, sync::mpsc, @@ -21,16 +24,18 @@ use tokio_stream::wrappers::UnboundedReceiverStream; use tonic::{ codegen::InterceptedService, metadata::{Ascii, MetadataValue}, - service::Interceptor, + service::{Interceptor, InterceptorLayer}, transport::{Certificate, Channel, ClientTlsConfig, Endpoint}, Request, Status, Streaming, }; +use tower::ServiceBuilder; +use tracing::{instrument, Instrument}; use crate::{ config::Config, enterprise::firewall::{ api::{FirewallApi, FirewallManagementApi}, - FirewallConfig, FirewallRule, + FirewallConfig, FirewallRule, SnatBinding, }, error::GatewayError, execute_command, mask, @@ -38,6 +43,7 @@ use crate::{ gateway_service_client::GatewayServiceClient, stats_update::Payload, update, Configuration, ConfigurationRequest, Peer, StatsUpdate, Update, }, + version::ensure_core_version_supported, VERSION, }; @@ -69,12 +75,21 @@ impl From for InterfaceConfiguration { } } -#[derive(Clone)] +/// Intercepts all grpc requests adding authentication and version metadata struct AuthInterceptor { hostname: MetadataValue, token: MetadataValue, } +impl Clone for AuthInterceptor { + fn clone(&self) -> Self { + Self { + hostname: self.hostname.clone(), + token: self.token.clone(), + } + } +} + impl AuthInterceptor { fn new(token: &str) -> Result { let token = MetadataValue::try_from(token)?; @@ -90,6 +105,7 @@ impl AuthInterceptor { impl Interceptor for AuthInterceptor { fn call(&mut self, mut request: Request<()>) -> Result, Status> { + // Add auth headers let metadata = request.metadata_mut(); metadata.insert("authorization", self.token.clone()); metadata.insert("hostname", self.hostname.clone()); @@ -99,6 +115,9 @@ impl Interceptor for AuthInterceptor { } type PubKey = String; +type GatewayClientType = GatewayServiceClient< + InterceptedService, ClientVersionInterceptor>, +>; pub struct Gateway { config: Config, @@ -110,7 +129,8 @@ pub struct Gateway { #[cfg_attr(not(target_os = "linux"), allow(unused))] firewall_config: Option, pub connected: Arc, - client: GatewayServiceClient>, + client: GatewayClientType, + core_info: Option, stats_thread: Option>, } @@ -131,6 +151,7 @@ impl Gateway { stats_thread: None, firewall_api, firewall_config: None, + core_info: None, }) } @@ -182,6 +203,7 @@ impl Gateway { } /// Starts tokio thread collecting stats and sending them to backend service via gRPC. + #[instrument(skip_all)] fn spawn_stats_thread(&mut self) -> UnboundedReceiverStream { if let Some(handle) = self.stats_thread.take() { debug!("Aborting previous stats thread before starting a new one"); @@ -192,63 +214,67 @@ impl Gateway { let wgapi = Arc::clone(&self.wgapi); let (tx, rx) = mpsc::unbounded_channel(); debug!("Spawning stats thread"); - let handle = spawn(async move { - // helper map to track if peer data is actually changing - // and avoid sending duplicate stats - let mut peer_map = HashMap::new(); - let mut interval = interval(period); - let mut id = 1; - 'outer: loop { - // wait until next iteration - interval.tick().await; - debug!("Sending active peer stats updates."); - let interface_data = wgapi.lock().unwrap().read_interface_data(); - match interface_data { - Ok(host) => { - let peers = host.peers; - debug!( - "Found {} peers configured on WireGuard interface", - peers.len() - ); - for peer in peers.into_values().filter(|p| { - p.last_handshake - .is_some_and(|lhs| lhs != SystemTime::UNIX_EPOCH) - }) { - let has_changed = peer_map - .get(&peer.public_key) - .is_none_or(|last_peer| *last_peer != peer); - if has_changed { - peer_map.insert(peer.public_key.clone(), peer.clone()); - id += 1; - if tx - .send(StatsUpdate { - id, - payload: Some(Payload::PeerStats((&peer).into())), - }) - .is_err() - { - debug!("Stats stream disappeared"); - break 'outer; + let handle = spawn( + async move { + // helper map to track if peer data is actually changing + // and avoid sending duplicate stats + let mut peer_map = HashMap::new(); + let mut interval = interval(period); + let mut id = 1; + 'outer: loop { + // wait until next iteration + interval.tick().await; + debug!("Sending active peer stats updates."); + let interface_data = wgapi.lock().unwrap().read_interface_data(); + match interface_data { + Ok(host) => { + let peers = host.peers; + debug!( + "Found {} peers configured on WireGuard interface", + peers.len() + ); + for peer in peers.into_values().filter(|p| { + p.last_handshake + .is_some_and(|lhs| lhs != SystemTime::UNIX_EPOCH) + }) { + let has_changed = peer_map + .get(&peer.public_key) + .is_none_or(|last_peer| *last_peer != peer); + if has_changed { + peer_map.insert(peer.public_key.clone(), peer.clone()); + id += 1; + if tx + .send(StatsUpdate { + id, + payload: Some(Payload::PeerStats((&peer).into())), + }) + .is_err() + { + debug!("Stats stream disappeared"); + break 'outer; + } + } else { + debug!( + "Stats for peer {} have not changed. Skipping.", + peer.public_key + ); } - } else { - debug!( - "Stats for peer {} have not changed. Skipping.", - peer.public_key - ); } } + Err(err) => error!("Failed to retrieve WireGuard interface stats: {err}"), } - Err(err) => error!("Failed to retrieve WireGuard interface stats: {err}"), + debug!("Sent peer stats updates for all peers."); } - debug!("Sent peer stats updates for all peers."); } - }); + .instrument(tracing::Span::current()), + ); self.stats_thread = Some(handle); UnboundedReceiverStream::new(rx) } + #[instrument(skip_all)] async fn handle_stats_thread( - mut client: GatewayServiceClient>, + mut client: GatewayClientType, rx: UnboundedReceiverStream, ) { let status = client.stats(rx).await; @@ -262,7 +288,8 @@ impl Gateway { fn has_firewall_config_changed(&self, new_fw_config: &FirewallConfig) -> bool { if let Some(current_config) = &self.firewall_config { return current_config.default_policy != new_fw_config.default_policy - || self.have_firewall_rules_changed(&new_fw_config.rules); + || self.have_firewall_rules_changed(&new_fw_config.rules) + || self.have_snat_bindings_changed(&new_fw_config.snat_bindings); } true @@ -292,18 +319,62 @@ impl Gateway { } } - debug!("Defguard ACL rules are the same. Rules have not changed. My rules: {current_rules:?}, new rules: {new_rules:?}"); + debug!( + "Defguard ACL rules are the same. Rules have not changed. My rules: \ + {current_rules:?}, new rules: {new_rules:?}" + ); + false + } else { + debug!( + "There are new Defguard ACL rules in the new configuration, but we don't have \ + any in the current one. Rules have changed." + ); + true + } + } + + /// Checks whether SNAT bindings have changed. + fn have_snat_bindings_changed(&self, new_bindings: &[SnatBinding]) -> bool { + debug!("Checking if SNAT bindings have changed"); + if let Some(current_config) = &self.firewall_config { + let current_bindings = ¤t_config.snat_bindings; + if current_bindings.len() != new_bindings.len() { + debug!("Number of SNAT bindings is different, so the bindings have changed"); + return true; + } + + for binding in new_bindings { + if !current_bindings.contains(binding) { + debug!("Found a new SNAT binding: {binding:?}. Bindings have changed."); + return true; + } + } + + for binding in current_bindings { + if !new_bindings.contains(binding) { + debug!("Found a removed SNAT binding: {binding:?}. Bindings have changed."); + return true; + } + } + + debug!( + "SNAT bindings are the same. Bindings have not changed. My bindings: \ + {current_bindings:?}, new bindings: {new_bindings:?}" + ); false } else { - debug!("There are new Defguard ACL rules in the new configuration, but we don't have any in the current one. Rules have changed."); + debug!( + "There are new SNAT bindings in the new configuration, but we don't have any in \ + the current one. Bindings have changed." + ); true } } /// Process and apply firewall configuration changes. /// - If the main config changed (default policy), reconfigure the whole firewall. - /// - If only the rules changed, apply the new rules. Currently also reconfigures the whole firewall but that - /// should be temporary. + /// - If only the rules changed, apply the new rules. Currently also reconfigures the whole + /// firewall but that should be temporary. /// /// TODO: Reduce cloning here fn process_firewall_changes( @@ -313,27 +384,30 @@ impl Gateway { if let Some(fw_config) = fw_config { debug!("Received firewall configuration: {fw_config:?}"); if self.has_firewall_config_changed(fw_config) { - debug!("Received firewall configuration is different than current one. Reconfiguring firewall..."); + debug!( + "Received firewall configuration is different than current one. \ + Reconfiguring firewall..." + ); self.firewall_api.begin()?; self.firewall_api .setup(fw_config.default_policy, self.config.fw_priority)?; - if self.config.masquerade { - self.firewall_api.set_masquerade_status(true)?; - } + self.firewall_api + .setup_nat(self.config.masquerade, &fw_config.snat_bindings)?; self.firewall_api.add_rules(fw_config.rules.clone())?; self.firewall_api.commit()?; self.firewall_config = Some(fw_config.clone()); info!("Reconfigured firewall with new configuration"); } else { - debug!("Received firewall configuration is the same as current one. Skipping reconfiguration."); + debug!( + "Received firewall configuration is the same as current one. Skipping \ + reconfiguration." + ); } } else { debug!("Received firewall configuration is empty, cleaning up firewall rules..."); self.firewall_api.begin()?; self.firewall_api.cleanup()?; - if self.config.masquerade { - self.firewall_api.set_masquerade_status(true)?; - } + self.firewall_api.setup_nat(self.config.masquerade, &[])?; self.firewall_api.commit()?; self.firewall_config = None; debug!("Cleaned up firewall rules"); @@ -361,7 +435,7 @@ impl Gateway { if self.is_interface_config_changed(&new_interface_configuration, &new_configuration.peers) { debug!( - "Received configuration is different than the current one. Reconfiguring interface..." + "Received configuration is different than the current one. Reconfiguring interface." ); self.wgapi .lock() @@ -379,11 +453,16 @@ impl Gateway { self.interface_configuration = Some(new_interface_configuration); self.replace_peers(new_configuration.peers); } else { - debug!("Received configuration is identical to the current one. Skipping interface reconfiguration."); + debug!( + "Received configuration is identical to the current one. Skipping interface \ + reconfiguration." + ); } // process received firewall config unless firewall management is disabled - if !self.config.disable_firewall_management { + if self.config.disable_firewall_management { + debug!("Firewall management is disabled. Skipping updating firewall configuration"); + } else { let new_firewall_configuration = if let Some(firewall_config) = new_configuration.firewall_config { Some(FirewallConfig::from_proto(firewall_config)?) @@ -392,8 +471,6 @@ impl Gateway { }; self.process_firewall_changes(new_firewall_configuration.as_ref())?; - } else { - debug!("Firewall management is disabled. Skipping updating firewall configuration"); } Ok(()) @@ -421,6 +498,20 @@ impl Gateway { }; match (response, stream) { (Ok(response), Ok(stream)) => { + self.core_info = ComponentInfo::from_metadata(response.metadata()); + let (version, info) = get_tracing_variables(&self.core_info); + let span = tracing::info_span!( + "core_configuration", + component = %DefguardComponent::Core, + version = version.to_string(), + info + ); + let _guard = span.enter(); + + // check core version and exit if it's not supported + let version = self.core_info.as_ref().map(|info| &info.version); + ensure_core_version_supported(version); + if let Err(err) = self.configure(response.into_inner()) { error!("Interface configuration failed: {err}"); continue; @@ -433,22 +524,25 @@ impl Gateway { break stream.into_inner(); } (Err(err), _) => { - error!("Couldn't retrieve gateway configuration from the core. Using gRPC URL: {}. Retrying in 10s. Error: {err}", - self.config.grpc_url); + error!( + "Couldn't retrieve gateway configuration from the core. Using gRPC URL: \ + {}. Retrying in 10s. Error: {err}", + self.config.grpc_url + ); } (_, Err(err)) => { - error!("Couldn't establish streaming connection to the core. Using gRPC URL: {}. Retrying in 10s. Error: {err}", - self.config.grpc_url); + error!( + "Couldn't establish streaming connection to the core. Using gRPC URL: \ + {}. Retrying in 10s. Error: {err}", + self.config.grpc_url + ); } } sleep(TEN_SECS).await; } } - fn setup_client( - config: &Config, - ) -> Result>, GatewayError> - { + fn setup_client(config: &Config) -> Result { debug!("Preparing gRPC client configuration"); let tls = ClientTlsConfig::new(); // Use CA if provided, otherwise load certificates from system. @@ -467,14 +561,19 @@ impl Gateway { .keep_alive_while_idle(true) .tls_config(tls)?; let channel = endpoint.connect_lazy(); - + let version_interceptor = ClientVersionInterceptor::new(Version::parse(VERSION)?); let auth_interceptor = AuthInterceptor::new(&config.token)?; - let client = GatewayServiceClient::with_interceptor(channel, auth_interceptor); + let channel = ServiceBuilder::new() + .layer(InterceptorLayer::new(version_interceptor)) + .layer(InterceptorLayer::new(auth_interceptor)) + .service(channel); + let client = GatewayServiceClient::new(channel); debug!("gRPC client configuration done"); Ok(client) } + #[instrument(skip_all)] async fn handle_updates(&mut self, updates_stream: &mut Streaming) { loop { match updates_stream.message().await { @@ -518,7 +617,10 @@ impl Gateway { } Some(update::Update::FirewallConfig(config)) => { if self.config.disable_firewall_management { - debug!("Received firewall config update, but firewall management is disabled. Skipping processing this update: {config:?}"); + debug!( + "Received firewall config update, but firewall management \ + is disabled. Skipping processing this update: {config:?}" + ); continue; } @@ -550,7 +652,10 @@ impl Gateway { } Some(update::Update::DisableFirewall(())) => { if self.config.disable_firewall_management { - debug!("Received firewall disable request, but firewall management is disabled. Skipping processing this update"); + debug!( + "Received firewall disable request, but firewall management \ + is disabled. Skipping processing this update" + ); continue; } @@ -597,13 +702,13 @@ impl Gateway { #[cfg(target_os = "linux")] if !self.config.disable_firewall_management && self.config.masquerade { self.firewall_api.begin()?; - self.firewall_api.set_masquerade_status(true)?; + self.firewall_api.setup_nat(self.config.masquerade, &[])?; self.firewall_api.commit()?; } } info!( - "Trying to connect to {} and obtain the gateway configuration from Defguard...", + "Trying to connect to {} and obtain the gateway configuration from Defguard.", self.config.grpc_url ); loop { @@ -612,6 +717,14 @@ impl Gateway { debug!("Executing specified POST_UP command: {post_up}"); execute_command(post_up)?; } + let (version, info) = get_tracing_variables(&self.core_info); + let span = tracing::info_span!( + "core_grpc", + component = %DefguardComponent::Core, + version = version.to_string(), + info, + ); + let _guard = span.enter(); let stats_stream = self.spawn_stats_thread(); let client = self.client.clone(); select! { @@ -629,7 +742,7 @@ impl Gateway { #[cfg(test)] mod tests { - use std::net::Ipv4Addr; + use std::{net::Ipv4Addr, slice::from_ref}; #[cfg(not(any(target_os = "macos", target_os = "netbsd")))] use defguard_wireguard_rs::Kernel; @@ -687,6 +800,7 @@ mod tests { stats_thread: None, firewall_api, firewall_config: None, + core_info: None, }; // new config is the same @@ -852,11 +966,13 @@ mod tests { let config1 = FirewallConfig { rules: vec![rule1.clone(), rule2.clone()], default_policy: Policy::Allow, + snat_bindings: vec![], }; let config_empty = FirewallConfig { rules: Vec::new(), default_policy: Policy::Allow, + snat_bindings: vec![], }; #[cfg(target_os = "macos")] @@ -876,6 +992,7 @@ mod tests { stats_thread: None, firewall_api: FirewallApi::new("test_interface").unwrap(), firewall_config: None, + core_info: None, }; // Gateway has no firewall config, new rules are empty @@ -884,7 +1001,7 @@ mod tests { // Gateway has no firewall config, but new rules exist gateway.firewall_config = None; - assert!(gateway.have_firewall_rules_changed(&[rule1.clone()])); + assert!(gateway.have_firewall_rules_changed(from_ref(&rule1))); // Gateway has firewall config, with empty rules list gateway.firewall_config = Some(config1.clone()); @@ -892,7 +1009,7 @@ mod tests { // Gateway has firewall config, new rules have different length gateway.firewall_config = Some(config1.clone()); - assert!(gateway.have_firewall_rules_changed(&[rule1.clone()])); + assert!(gateway.have_firewall_rules_changed(from_ref(&rule1))); // Gateway has firewall config, new rules have different content gateway.firewall_config = Some(config1.clone()); @@ -904,7 +1021,7 @@ mod tests { // Gateway has empty firewall config, new rules exist gateway.firewall_config = Some(config_empty.clone()); - assert!(gateway.have_firewall_rules_changed(&[rule1.clone()])); + assert!(gateway.have_firewall_rules_changed(from_ref(&rule1))); // Both configs are empty gateway.firewall_config = Some(config_empty); @@ -916,16 +1033,19 @@ mod tests { let config1 = FirewallConfig { rules: Vec::new(), default_policy: Policy::Allow, + snat_bindings: vec![], }; let config2 = FirewallConfig { rules: Vec::new(), default_policy: Policy::Deny, + snat_bindings: vec![], }; let config3 = FirewallConfig { rules: Vec::new(), default_policy: Policy::Allow, + snat_bindings: vec![], }; #[cfg(target_os = "macos")] @@ -945,6 +1065,7 @@ mod tests { stats_thread: None, firewall_api: FirewallApi::new("test_interface").unwrap(), firewall_config: None, + core_info: None, }; // Gateway has no config gateway.firewall_config = None; @@ -971,6 +1092,7 @@ mod tests { ipv4: true, }], default_policy: Policy::Allow, + snat_bindings: vec![], }; gateway.firewall_config = Some(config1); assert!(gateway.has_firewall_config_changed(&config4)); @@ -988,6 +1110,7 @@ mod tests { ipv4: false, }], default_policy: Policy::Allow, + snat_bindings: vec![], }; gateway.firewall_config = Some(config4); assert!(gateway.has_firewall_config_changed(&config5)); diff --git a/src/lib.rs b/src/lib.rs index 251696cf..66c5ef14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ pub mod config; pub mod error; pub mod gateway; pub mod server; +mod version; pub mod proto { pub mod gateway { @@ -26,7 +27,7 @@ use syslog::{BasicLogger, Facility, Formatter3164}; pub mod enterprise; -pub const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "-", env!("VERGEN_GIT_SHA")); +pub const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "+", env!("VERGEN_GIT_SHA")); /// Masks object's field with "***" string. /// Used to log sensitive/secret objects. diff --git a/src/main.rs b/src/main.rs index b2270125..b9149666 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,12 +2,12 @@ use std::{fs::File, io::Write, process, sync::Arc}; use defguard_gateway::{ config::get_config, enterprise::firewall::api::FirewallApi, error::GatewayError, - execute_command, gateway::Gateway, init_syslog, server::run_server, + execute_command, gateway::Gateway, init_syslog, server::run_server, VERSION, }; +use defguard_version::Version; #[cfg(not(any(target_os = "macos", target_os = "netbsd")))] use defguard_wireguard_rs::Kernel; use defguard_wireguard_rs::{Userspace, WGApi}; -use env_logger::{init_from_env, Env, DEFAULT_FILTER_ENV}; use tokio::task::JoinSet; #[tokio::main] @@ -30,7 +30,8 @@ async fn main() -> Result<(), GatewayError> { return Err(error); } } else { - init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); + let version = Version::parse(VERSION)?; + defguard_version::tracing::init(version, &config.log_level)?; } if let Some(pre_up) = &config.pre_up { @@ -59,7 +60,11 @@ async fn main() -> Result<(), GatewayError> { let mut tasks = JoinSet::new(); if let Some(health_port) = config.health_port { - tasks.spawn(run_server(health_port, Arc::clone(&gateway.connected))); + tasks.spawn(run_server( + health_port, + config.http_bind_address, + Arc::clone(&gateway.connected), + )); } tasks.spawn(async move { gateway.start().await }); while let Some(Ok(result)) = tasks.join_next().await { diff --git a/src/server.rs b/src/server.rs index f0ea62a8..2f25848b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -21,13 +21,20 @@ async fn healthcheck<'a>( } } -pub async fn run_server(http_port: u16, connected: Arc) -> Result<(), GatewayError> { +pub async fn run_server( + http_port: u16, + http_bind_address: Option, + connected: Arc, +) -> Result<(), GatewayError> { let app = Router::new() .route("/health", get(healthcheck)) .layer(Extension(connected)); // run server - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), http_port); + let addr = SocketAddr::new( + http_bind_address.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + http_port, + ); let listener = TcpListener::bind(&addr).await?; info!("Health check listening on {addr}"); serve(listener, app.into_make_service()) diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 00000000..754bc7f4 --- /dev/null +++ b/src/version.rs @@ -0,0 +1,19 @@ +use defguard_version::{is_version_lower, Version}; + +const MIN_CORE_VERSION: Version = Version::new(1, 5, 0); + +/// Ensures the core version meets minimum version requirements. +/// Terminates the process if it doesn't. +pub(crate) fn ensure_core_version_supported(core_version: Option<&Version>) { + let Some(core_version) = core_version else { + error!("Missing core component version information. This most likely means that core component uses outdated version. Exiting."); + std::process::exit(1); + }; + + if is_version_lower(core_version, &MIN_CORE_VERSION) { + error!("Core version {core_version} is not supported. Minimal supported core version is {MIN_CORE_VERSION}. Exiting."); + std::process::exit(1); + } + + info!("Core version {core_version} is supported"); +}