diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 3e5956d9..1f21aa95 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -18,39 +18,45 @@ env: jobs: build-docker: runs-on: - - self-hosted - - Linux - - ${{ matrix.runner }} + - codebuild-defguard-gateway-runner-${{ github.run_id }}-${{ github.run_attempt }} + image:${{ matrix.os }} + instance-size:${{ matrix.size }} strategy: matrix: - cpu: [arm64, amd64, arm/v7] include: - - cpu: arm64 - runner: ARM64 + - os: arm-3.0 + size: xlarge + cpu: arm64 tag: arm64 - - cpu: amd64 - runner: X64 + - os: ubuntu-7.0 + size: xlarge + cpu: amd64 tag: amd64 - - cpu: arm/v7 - runner: ARM + - os: arm-3.0 + size: xlarge + cpu: arm/v7 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: @@ -59,13 +65,35 @@ jobs: provenance: false push: true tags: "${{ env.GHCR_REPO }}:${{ github.sha }}-${{ matrix.tag }}" - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: | + type=registry,ref=${{ env.GHCR_REPO }}:cache-${{ matrix.tag }} + type=registry,ref=${{ env.GHCR_REPO }}:cache-${{ matrix.tag }}-${{ github.ref_name }} + cache-to: type=registry,mode=max,ref=${{ env.GHCR_REPO }}:cache-${{ matrix.tag }}-${{ github.ref_name }} + + - 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 +102,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 +119,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 3b7558a2..51c5e6cd 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.0" +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=8649a9ba225d7bd2066a09c9e1347705c34bd158#8649a9ba225d7bd2066a09c9e1347705c34bd158" +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.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +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 8f7e50b6..967c7a08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,31 +1,38 @@ [package] name = "defguard-gateway" -version = "1.4.0" +version = "1.5.0" edition = "2021" [dependencies] -axum = { version = "0.8", features = ["macros"] } +defguard_version = { git = "https://github.com/DefGuard/defguard.git", rev = "8649a9ba225d7bd2066a09c9e1347705c34bd158" } +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 d27a3ccb..2c2d714c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ 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 b227bc8e..abf59ab7 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1751271578, - "narHash": "sha256-P/SQmKDu06x8yv7i0s8bvnnuJYkxVGBWLWHaU+tt4YY=", + "lastModified": 1757347588, + "narHash": "sha256-tLdkkC6XnsY9EOZW9TlpesTclELy8W7lL2ClL+nma8o=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3016b4b15d13f3089db8a41ef937b13a9e33a8df", + "rev": "b599843bad24621dcaa5ab60dac98f9b0eb1cabe", "type": "github" }, "original": { @@ -48,11 +48,11 @@ ] }, "locked": { - "lastModified": 1751423951, - "narHash": "sha256-AowKhJGplXRkAngSvb+32598DTiI6LOzhAnzgvbCtYM=", + "lastModified": 1757471515, + "narHash": "sha256-0+rSzNsYindDWjO9VVULKGjXlPsQV6IDjRU5G3SwI9U=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "1684ed5b15859b655caf41b467d046e29a994d04", + "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 c0aef683..883487df 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit c0aef68395720f46a7f038b6766de3bb30e02930 +Subproject commit 883487df67d90fd14fae900737cd8b5ea6c10de3 diff --git a/src/config.rs b/src/config.rs index 8235fe46..00445ffa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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 @@ -113,6 +128,7 @@ pub struct Config { impl Default for Config { fn default() -> Self { Self { + log_level: "info".into(), token: "TOKEN".into(), name: None, grpc_url: "http://localhost:50051".into(), @@ -146,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/iprange.rs b/src/enterprise/firewall/iprange.rs index 33eab39d..b1b4acd6 100644 --- a/src/enterprise/firewall/iprange.rs +++ b/src/enterprise/firewall/iprange.rs @@ -76,6 +76,23 @@ impl IpAddrRange { Self::V6(_) => true, } } + + /// Returns the start of the range. + pub fn start(&self) -> IpAddr { + match self { + Self::V4(range) => IpAddr::V4(*range.start()), + Self::V6(range) => IpAddr::V6(*range.start()), + } + } + + /// Returns the end of the range. + /// If the range is empty, returns the start of the range. + pub fn end(&self) -> IpAddr { + match self { + Self::V4(range) => IpAddr::V4(*range.end()), + Self::V6(range) => IpAddr::V6(*range.end()), + } + } } impl Iterator for IpAddrRange { diff --git a/src/enterprise/firewall/mod.rs b/src/enterprise/firewall/mod.rs index 532dd9f0..e078af43 100644 --- a/src/enterprise/firewall/mod.rs +++ b/src/enterprise/firewall/mod.rs @@ -24,6 +24,24 @@ pub(crate) enum Address { } impl Address { + // FIXME: remove after merging nft hotfix into dev + #[allow(dead_code)] + pub fn first(&self) -> IpAddr { + match self { + Address::Network(network) => network.ip(), + Address::Range(range) => range.start(), + } + } + + // FIXME: remove after merging nft hotfix into dev + #[allow(dead_code)] + pub fn last(&self) -> IpAddr { + match self { + Address::Network(network) => max_address(network), + Address::Range(range) => range.end(), + } + } + pub fn from_proto(ip: &proto::enterprise::firewall::IpAddress) -> Result { match &ip.address { Some(proto::enterprise::firewall::ip_address::Address::Ip(ip)) => { @@ -359,3 +377,85 @@ pub enum FirewallError { #[error("Firewall transaction failed: {0}")] TransactionFailed(String), } + +/// Get the max address in a network. +/// +/// - 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) => { + let addr = network.ip().to_bits(); + let mask = network.mask().to_bits(); + + IpAddr::V4(Ipv4Addr::from(addr | !mask)) + } + IpNetwork::V6(network) => { + let addr = network.ip().to_bits(); + let mask = network.mask().to_bits(); + + IpAddr::V6(Ipv6Addr::from(addr | !mask)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_max_address_ipv4_24() { + let network = IpNetwork::V4(Ipv4Network::from_str("192.168.1.0/24").unwrap()); + let max = max_address(&network); + assert_eq!(max, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255))); + } + + #[test] + fn test_max_address_ipv4_16() { + let network = IpNetwork::V4(Ipv4Network::from_str("10.1.0.0/16").unwrap()); + let max = max_address(&network); + assert_eq!(max, IpAddr::V4(Ipv4Addr::new(10, 1, 255, 255))); + } + + #[test] + fn test_max_address_ipv4_8() { + let network = IpNetwork::V4(Ipv4Network::from_str("172.16.0.0/8").unwrap()); + let max = max_address(&network); + assert_eq!(max, IpAddr::V4(Ipv4Addr::new(172, 255, 255, 255))); + } + + #[test] + fn test_max_address_ipv4_32() { + let network = IpNetwork::V4(Ipv4Network::from_str("192.168.1.1/32").unwrap()); + let max = max_address(&network); + assert_eq!(max, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))); + } + + #[test] + fn test_max_address_ipv6_64() { + let network = IpNetwork::V6(Ipv6Network::from_str("2001:db8::/64").unwrap()); + let max = max_address(&network); + assert_eq!( + max, + IpAddr::V6(Ipv6Addr::from_str("2001:db8::ffff:ffff:ffff:ffff").unwrap()) + ); + } + + #[test] + fn test_max_address_ipv6_128() { + let network = IpNetwork::V6(Ipv6Network::from_str("2001:db8::1/128").unwrap()); + let max = max_address(&network); + assert_eq!(max, IpAddr::V6(Ipv6Addr::from_str("2001:db8::1").unwrap())); + } + + #[test] + fn test_max_address_ipv6_48() { + let network = IpNetwork::V6(Ipv6Network::from_str("2001:db8:1234::/48").unwrap()); + let max = max_address(&network); + assert_eq!( + max, + IpAddr::V6(Ipv6Addr::from_str("2001:db8:1234:ffff:ffff:ffff:ffff:ffff").unwrap()) + ); + } +} diff --git a/src/enterprise/firewall/nftables/mod.rs b/src/enterprise/firewall/nftables/mod.rs index a5bd53ec..94af5e00 100644 --- a/src/enterprise/firewall/nftables/mod.rs +++ b/src/enterprise/firewall/nftables/mod.rs @@ -1,6 +1,9 @@ pub mod netfilter; -use std::sync::atomic::{AtomicU32, Ordering}; +use std::{ + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + sync::atomic::{AtomicU32, Ordering}, +}; use netfilter::{ allow_established_traffic, apply_filter_rules, drop_table, ignore_unrelated_traffic, @@ -10,8 +13,10 @@ use nftnl::Batch; use super::{ api::{FirewallApi, FirewallManagementApi}, + iprange::IpAddrRangeError, Address, FirewallError, FirewallRule, Policy, Port, Protocol, SnatBinding, }; +use crate::enterprise::firewall::iprange::IpAddrRange; static SET_ID_COUNTER: AtomicU32 = AtomicU32::new(0); @@ -51,29 +56,110 @@ struct FilterRule<'a> { negated_iifname: bool, } +/// Merges any contiguous subets or addres ranges into an address range. +/// +/// This reflects the way `nft` CLI handles such cases. +/// Otherwise first address in any subnet after the first is not matched. +/// For example if we use `172.30.0.2/31, 172.30.0.4/31` as `saddr` in a rule, +/// then 172.30.0.4 will not be matched. +fn merge_addrs(addrs: Vec
) -> Result, IpAddrRangeError> { + debug!("Merging any contiguous subnets and ranges found within address list: {addrs:?}"); + + if addrs.is_empty() { + debug!("No addresses provided, returning empty vector."); + return Ok(Vec::new()); + } + + let mut merged_addrs = Vec::new(); + let mut current_address = None; + + // we can assume addresses coming from the core + // are already sorted and non-overlapping + for next_address in addrs { + match ¤t_address { + None => { + debug!("Initializing current address with: {next_address:?}"); + current_address = Some(next_address); + } + Some(previous_address) => { + let previous_range_start = previous_address.first(); + let previous_range_end = previous_address.last(); + let next_ip = next_ip(previous_range_end); + + let next_range_start = next_address.first(); + let next_range_end = next_address.last(); + + // check if range is adjacent to current address + if next_range_start == next_ip { + // replace current address with a combined range + debug!("Merging {next_address:?} with {current_address:?}"); + current_address = Some(Address::Range(IpAddrRange::new( + previous_range_start, + next_range_end, + )?)); + } else { + // push previous address to result and replace with next address + merged_addrs.push(previous_address.clone()); + current_address = Some(next_address); + }; + } + } + } + + // push last remaining address to results + if let Some(address) = current_address { + debug!("Pushing last remaining address into results: {address:?}"); + merged_addrs.push(address) + } + + debug!("Prepared addresses: {merged_addrs:?}"); + + Ok(merged_addrs) +} + +/// Returns the next IP address in sequence, handling overflow via wrapping +fn next_ip(ip: IpAddr) -> IpAddr { + match ip { + IpAddr::V4(ipv4) => { + let ip_u32 = ipv4.to_bits(); + let next_ip_u32 = ip_u32.wrapping_add(1); + IpAddr::V4(Ipv4Addr::from(next_ip_u32)) + } + IpAddr::V6(ipv6) => { + let ip_u128 = ipv6.to_bits(); + let next_ip_u128 = ip_u128.wrapping_add(1); + IpAddr::V6(Ipv6Addr::from(next_ip_u128)) + } + } +} + impl FirewallApi { fn add_rule(&mut self, rule: FirewallRule) -> Result<(), FirewallError> { debug!("Applying the following Defguard ACL rule: {rule:?}"); - let mut rules = Vec::new(); let batch = if let Some(ref mut batch) = self.batch { batch } else { return Err(FirewallError::TransactionNotStarted); }; + let mut filter_rules = Vec::new(); debug!( "The rule will be split into multiple nftables rules based on the specified \ destination ports and protocols." ); + + let source_addrs = merge_addrs(rule.source_addrs)?; + let dest_addrs = merge_addrs(rule.destination_addrs)?; + if rule.destination_ports.is_empty() { debug!( "No destination ports specified, applying single aggregate nftables rule for \ every protocol." ); let rule = FilterRule { - src_ips: &rule.source_addrs, - dest_ips: &rule.destination_addrs, - protocols: rule.protocols, + src_ips: &source_addrs, + dest_ips: &dest_addrs, + protocols: rule.protocols.clone(), action: rule.verdict, counter: true, defguard_rule_id: rule.id, @@ -81,47 +167,35 @@ impl FirewallApi { comment: rule.comment.clone(), ..Default::default() }; - rules.push(rule); + filter_rules.push(rule); } else if !rule.protocols.is_empty() { debug!( "Destination ports and protocols specified, applying individual nftables rules \ for each protocol." ); - for protocol in rule.protocols { + for protocol in rule.protocols.clone() { debug!("Applying rule for protocol: {protocol:?}"); + let mut filter_rule = FilterRule { + src_ips: &source_addrs, + dest_ips: &dest_addrs, + protocols: vec![protocol], + action: rule.verdict, + counter: true, + defguard_rule_id: rule.id, + v4: rule.ipv4, + comment: rule.comment.clone(), + ..Default::default() + }; if protocol.supports_ports() { debug!("Protocol supports ports, rule."); - let rule = FilterRule { - src_ips: &rule.source_addrs, - dest_ips: &rule.destination_addrs, - dest_ports: &rule.destination_ports, - protocols: vec![protocol], - action: rule.verdict, - counter: true, - defguard_rule_id: rule.id, - v4: rule.ipv4, - comment: rule.comment.clone(), - ..Default::default() - }; - rules.push(rule); + filter_rule.dest_ports = &rule.destination_ports; } else { debug!( "Protocol does not support ports, applying nftables rule and ignoring \ destination ports." ); - let rule = FilterRule { - src_ips: &rule.source_addrs, - dest_ips: &rule.destination_addrs, - protocols: vec![protocol], - action: rule.verdict, - counter: true, - defguard_rule_id: rule.id, - v4: rule.ipv4, - comment: rule.comment.clone(), - ..Default::default() - }; - rules.push(rule); } + filter_rules.push(filter_rule); } } else { debug!( @@ -131,8 +205,8 @@ impl FirewallApi { for protocol in [Protocol::Tcp, Protocol::Udp] { debug!("Applying nftables rule for protocol: {protocol:?}"); let rule = FilterRule { - src_ips: &rule.source_addrs, - dest_ips: &rule.destination_addrs, + src_ips: &source_addrs, + dest_ips: &dest_addrs, dest_ports: &rule.destination_ports, protocols: vec![protocol], action: rule.verdict, @@ -142,11 +216,11 @@ impl FirewallApi { comment: rule.comment.clone(), ..Default::default() }; - rules.push(rule); + filter_rules.push(rule); } } - apply_filter_rules(rules, batch, &self.ifname)?; + apply_filter_rules(filter_rules, batch, &self.ifname)?; debug!( "Applied firewall rules for Defguard ACL rule ID: {}", @@ -250,3 +324,265 @@ impl FirewallManagementApi for FirewallApi { } } } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use ipnetwork::IpNetwork; + + use super::*; + + #[test] + fn test_sorting() { + let mut addrs = vec![ + Address::Network(IpNetwork::from_str("10.10.10.11/24").unwrap()), + Address::Network(IpNetwork::from_str("10.10.10.12/24").unwrap()), + Address::Network(IpNetwork::from_str("10.10.11.10/32").unwrap()), + Address::Network(IpNetwork::from_str("10.10.11.11/32").unwrap()), + Address::Network(IpNetwork::from_str("10.10.10.10/24").unwrap()), + Address::Network(IpNetwork::from_str("10.10.11.12/32").unwrap()), + ]; + + addrs.sort_by(|a, b| { + a.first() + .partial_cmp(&b.first()) + .unwrap_or(std::cmp::Ordering::Equal) + }); + + assert_eq!( + addrs, + vec![ + Address::Network(IpNetwork::from_str("10.10.10.10/24").unwrap()), + Address::Network(IpNetwork::from_str("10.10.10.11/24").unwrap()), + Address::Network(IpNetwork::from_str("10.10.10.12/24").unwrap()), + Address::Network(IpNetwork::from_str("10.10.11.10/32").unwrap()), + Address::Network(IpNetwork::from_str("10.10.11.11/32").unwrap()), + Address::Network(IpNetwork::from_str("10.10.11.12/32").unwrap()), + ] + ); + + let _prepared_addrs = merge_addrs(addrs).unwrap(); + } + + #[test] + fn test_merge_addrs_empty() { + let addrs: Vec
= vec![]; + let result = merge_addrs(addrs).unwrap(); + assert!(result.is_empty()); + } + + #[test] + fn test_merge_addrs_single_address() { + let addrs = vec![Address::Network( + IpNetwork::from_str("192.168.1.10/32").unwrap(), + )]; + let result = merge_addrs(addrs.clone()).unwrap(); + + assert_eq!(result, addrs); + } + + #[test] + fn test_merge_addrs_adjacent_ranges() { + let addrs = vec![ + Address::Range( + IpAddrRange::new( + IpAddr::from_str("192.168.1.10").unwrap(), + IpAddr::from_str("192.168.1.20").unwrap(), + ) + .unwrap(), + ), + Address::Range( + IpAddrRange::new( + IpAddr::from_str("192.168.1.21").unwrap(), + IpAddr::from_str("192.168.1.30").unwrap(), + ) + .unwrap(), + ), + ]; + let result = merge_addrs(addrs).unwrap(); + + assert_eq!(result.len(), 1); + if let Address::Range(range) = &result[0] { + assert_eq!(range.start(), IpAddr::from_str("192.168.1.10").unwrap()); + assert_eq!(range.end(), IpAddr::from_str("192.168.1.30").unwrap()); + } else { + panic!("Expected Address::Range"); + } + } + + #[test] + fn test_merge_addrs_adjacent_single_addresses() { + let addrs = vec![ + Address::Network(IpNetwork::from_str("192.168.1.10/32").unwrap()), + Address::Network(IpNetwork::from_str("192.168.1.11/32").unwrap()), + Address::Network(IpNetwork::from_str("192.168.1.12/32").unwrap()), + ]; + let result = merge_addrs(addrs).unwrap(); + + assert_eq!(result.len(), 1); + if let Address::Range(range) = &result[0] { + assert_eq!(range.start(), IpAddr::from_str("192.168.1.10").unwrap()); + assert_eq!(range.end(), IpAddr::from_str("192.168.1.12").unwrap()); + } else { + panic!("Expected Address::Range"); + } + } + + #[test] + fn test_merge_addrs_non_adjacent_ranges() { + let addrs = vec![ + Address::Range( + IpAddrRange::new( + IpAddr::from_str("192.168.1.10").unwrap(), + IpAddr::from_str("192.168.1.20").unwrap(), + ) + .unwrap(), + ), + Address::Range( + IpAddrRange::new( + IpAddr::from_str("192.168.1.30").unwrap(), + IpAddr::from_str("192.168.1.40").unwrap(), + ) + .unwrap(), + ), + ]; + let result = merge_addrs(addrs).unwrap(); + + assert_eq!(result.len(), 2); + if let Address::Range(range1) = &result[0] { + assert_eq!(range1.start(), IpAddr::from_str("192.168.1.10").unwrap()); + assert_eq!(range1.end(), IpAddr::from_str("192.168.1.20").unwrap()); + } else { + panic!("Expected Address::Range"); + } + if let Address::Range(range2) = &result[1] { + assert_eq!(range2.start(), IpAddr::from_str("192.168.1.30").unwrap()); + assert_eq!(range2.end(), IpAddr::from_str("192.168.1.40").unwrap()); + } else { + panic!("Expected Address::Range"); + } + } + + #[test] + fn test_merge_addrs_mixed_networks_and_ranges() { + let addrs = vec![ + Address::Network(IpNetwork::from_str("192.168.1.10/32").unwrap()), + Address::Range( + IpAddrRange::new( + IpAddr::from_str("192.168.1.11").unwrap(), + IpAddr::from_str("192.168.1.15").unwrap(), + ) + .unwrap(), + ), + Address::Network(IpNetwork::from_str("192.168.1.16/32").unwrap()), + ]; + let result = merge_addrs(addrs).unwrap(); + + assert_eq!(result.len(), 1); + if let Address::Range(range) = &result[0] { + assert_eq!(range.start(), IpAddr::from_str("192.168.1.10").unwrap()); + assert_eq!(range.end(), IpAddr::from_str("192.168.1.16").unwrap()); + } else { + panic!("Expected Address::Range"); + } + } + + #[test] + fn test_merge_addrs_non_adjacent_singles() { + let addrs = vec![ + Address::Network(IpNetwork::from_str("192.168.1.10/32").unwrap()), + Address::Network(IpNetwork::from_str("192.168.1.11/32").unwrap()), + Address::Network(IpNetwork::from_str("192.168.1.15/32").unwrap()), + Address::Network(IpNetwork::from_str("192.168.1.20/32").unwrap()), + ]; + let result = merge_addrs(addrs).unwrap(); + + // These should result in 3 separate ranges: 10-11, 15, 20 + let expected_addrs = vec![ + Address::Range( + IpAddrRange::new( + IpAddr::from_str("192.168.1.10").unwrap(), + IpAddr::from_str("192.168.1.11").unwrap(), + ) + .unwrap(), + ), + Address::Network(IpNetwork::from_str("192.168.1.15/32").unwrap()), + Address::Network(IpNetwork::from_str("192.168.1.20/32").unwrap()), + ]; + assert_eq!(result, expected_addrs); + } + + #[test] + fn test_merge_addrs_ipv6() { + let addrs = vec![ + Address::Network(IpNetwork::from_str("2001:db8::1/128").unwrap()), + Address::Network(IpNetwork::from_str("2001:db8::2/128").unwrap()), + Address::Network(IpNetwork::from_str("2001:db8::3/128").unwrap()), + ]; + let result = merge_addrs(addrs).unwrap(); + + assert_eq!(result.len(), 1); + if let Address::Range(range) = &result[0] { + assert_eq!(range.start(), IpAddr::from_str("2001:db8::1").unwrap()); + assert_eq!(range.end(), IpAddr::from_str("2001:db8::3").unwrap()); + } else { + panic!("Expected Address::Range"); + } + } + + #[test] + fn test_next_ip_ipv4() { + assert_eq!( + next_ip(IpAddr::from_str("192.168.1.10").unwrap()), + IpAddr::from_str("192.168.1.11").unwrap() + ); + + // Test overflow + assert_eq!( + next_ip(IpAddr::from_str("255.255.255.255").unwrap()), + IpAddr::from_str("0.0.0.0").unwrap() + ); + } + + #[test] + fn test_next_ip_ipv6() { + assert_eq!( + next_ip(IpAddr::from_str("2001:db8::1").unwrap()), + IpAddr::from_str("2001:db8::2").unwrap() + ); + + // Test overflow + assert_eq!( + next_ip(IpAddr::from_str("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap()), + IpAddr::from_str("::").unwrap() + ); + } + + #[test] + fn test_merge_addrs_gap() { + let addrs = vec![ + Address::Network(IpNetwork::from_str("192.168.1.1/32").unwrap()), + Address::Network(IpNetwork::from_str("192.168.1.100/32").unwrap()), + ]; + let result = merge_addrs(addrs.clone()).unwrap(); + + // Should not merge since there's a gap + assert_eq!(result, addrs); + + let addrs = vec![ + Address::Range( + IpAddrRange::new( + IpAddr::from_str("192.168.1.10").unwrap(), + IpAddr::from_str("192.168.1.20").unwrap(), + ) + .unwrap(), + ), + Address::Network(IpNetwork::from_str("192.168.1.100/32").unwrap()), + ]; + let result = merge_addrs(addrs.clone()).unwrap(); + + // Should not merge since there's a gap + assert_eq!(result, addrs); + } +} diff --git a/src/enterprise/firewall/nftables/netfilter.rs b/src/enterprise/firewall/nftables/netfilter.rs index 6cef7be1..06e2d62d 100644 --- a/src/enterprise/firewall/nftables/netfilter.rs +++ b/src/enterprise/firewall/nftables/netfilter.rs @@ -1,13 +1,8 @@ -#[cfg(test)] -use std::str::FromStr; use std::{ ffi::{CStr, CString}, net::{IpAddr, Ipv4Addr, Ipv6Addr}, }; -use ipnetwork::IpNetwork; -#[cfg(test)] -use ipnetwork::{Ipv4Network, Ipv6Network}; use nftnl::{ expr::{Expression, Immediate, InterfaceName, Nat, NatType, Register}, nft_expr, nftnl_sys, @@ -16,7 +11,7 @@ use nftnl::{ }; use super::{get_set_id, Address, FilterRule, Policy, Port, Protocol, State}; -use crate::enterprise::firewall::{iprange::IpAddrRange, FirewallError, SnatBinding}; +use crate::enterprise::firewall::{iprange::IpAddrRange, max_address, FirewallError, SnatBinding}; const FILTER_TABLE: &str = "filter"; const NAT_TABLE: &str = "nat"; @@ -905,32 +900,11 @@ fn socket_recv<'a>( } } -/// Get the max address in a network. -/// -/// - In IPv4 this is the broadcast address. -/// - In IPv6 this is just the last address in the network. -fn max_address(network: &IpNetwork) -> IpAddr { - match network { - IpNetwork::V4(network) => { - let addr = network.ip().to_bits(); - let mask = network.mask().to_bits(); - - IpAddr::V4(Ipv4Addr::from(addr | !mask)) - } - IpNetwork::V6(network) => { - let addr = network.ip().to_bits(); - let mask = network.mask().to_bits(); - - IpAddr::V6(Ipv6Addr::from(addr | !mask)) - } - } -} - fn new_anon_set( table: &Table, family: ProtoFamily, interval_set: bool, -) -> Result, FirewallError> +) -> Result, FirewallError> where T: SetKey, { @@ -1087,59 +1061,4 @@ mod tests { increment_bytes(&mut ip); assert_eq!(ip, [0, 0, 0, 0, 0, 0, 0, 0]); } - - #[test] - fn test_max_address_ipv4_24() { - let network = IpNetwork::V4(Ipv4Network::from_str("192.168.1.0/24").unwrap()); - let max = max_address(&network); - assert_eq!(max, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255))); - } - - #[test] - fn test_max_address_ipv4_16() { - let network = IpNetwork::V4(Ipv4Network::from_str("10.1.0.0/16").unwrap()); - let max = max_address(&network); - assert_eq!(max, IpAddr::V4(Ipv4Addr::new(10, 1, 255, 255))); - } - - #[test] - fn test_max_address_ipv4_8() { - let network = IpNetwork::V4(Ipv4Network::from_str("172.16.0.0/8").unwrap()); - let max = max_address(&network); - assert_eq!(max, IpAddr::V4(Ipv4Addr::new(172, 255, 255, 255))); - } - - #[test] - fn test_max_address_ipv4_32() { - let network = IpNetwork::V4(Ipv4Network::from_str("192.168.1.1/32").unwrap()); - let max = max_address(&network); - assert_eq!(max, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))); - } - - #[test] - fn test_max_address_ipv6_64() { - let network = IpNetwork::V6(Ipv6Network::from_str("2001:db8::/64").unwrap()); - let max = max_address(&network); - assert_eq!( - max, - IpAddr::V6(Ipv6Addr::from_str("2001:db8::ffff:ffff:ffff:ffff").unwrap()) - ); - } - - #[test] - fn test_max_address_ipv6_128() { - let network = IpNetwork::V6(Ipv6Network::from_str("2001:db8::1/128").unwrap()); - let max = max_address(&network); - assert_eq!(max, IpAddr::V6(Ipv6Addr::from_str("2001:db8::1").unwrap())); - } - - #[test] - fn test_max_address_ipv6_48() { - let network = IpNetwork::V6(Ipv6Network::from_str("2001:db8:1234::/48").unwrap()); - let max = max_address(&network); - assert_eq!( - max, - IpAddr::V6(Ipv6Addr::from_str("2001:db8:1234:ffff:ffff:ffff:ffff:ffff").unwrap()) - ); - } } diff --git a/src/enterprise/firewall/packetfilter/api.rs b/src/enterprise/firewall/packetfilter/api.rs index a3d7bcb7..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,7 +66,7 @@ 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(()) @@ -75,8 +75,8 @@ impl FirewallManagementApi for FirewallApi { /// Setup Network Address Translation using POSTROUTING chain rules fn setup_nat( &mut self, - masquerade_enabled: bool, - snat_bindings: &[SnatBinding], + _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 6495ceb6..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()); } 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 ce664717..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,10 +24,12 @@ 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, @@ -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; @@ -293,10 +319,16 @@ 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."); + 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 } } @@ -325,18 +357,24 @@ impl Gateway { } } - debug!("SNAT bindings are the same. Bindings have not changed. My bindings: {current_bindings:?}, new bindings: {new_bindings:?}"); + debug!( + "SNAT bindings are the same. Bindings have not changed. My bindings: \ + {current_bindings:?}, new bindings: {new_bindings:?}" + ); false } else { - debug!("There are new SNAT bindings in the new configuration, but we don't have any in the current one. Bindings 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( @@ -346,7 +384,10 @@ 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)?; @@ -357,7 +398,10 @@ impl Gateway { 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..."); @@ -391,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() @@ -409,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)?) @@ -422,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(()) @@ -451,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; @@ -463,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. @@ -497,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 { @@ -548,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; } @@ -580,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; } @@ -633,7 +708,7 @@ impl Gateway { } 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 { @@ -642,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! { @@ -659,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; @@ -717,6 +800,7 @@ mod tests { stats_thread: None, firewall_api, firewall_config: None, + core_info: None, }; // new config is the same @@ -908,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 @@ -916,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()); @@ -924,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()); @@ -936,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); @@ -980,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; 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 ce5b2317..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 { 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"); +}