mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-30 20:49:57 +00:00
Compare commits
6 Commits
main
...
task/align
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63e7a018c1 | ||
|
|
274a184135 | ||
|
|
0920fa1fa9 | ||
|
|
0151cbd3e3 | ||
|
|
46ec42b9a0 | ||
|
|
e1b686420a |
45
.github/dependabot.yml
vendored
45
.github/dependabot.yml
vendored
@@ -1,45 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
open-pull-requests-limit: 15
|
|
||||||
groups:
|
|
||||||
actions:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
ignore:
|
|
||||||
# git-town/action v1.3.x crashes on cyclic PR graphs (self-loop main->main
|
|
||||||
# fork PRs) via its topological-sort visualization. Pinned to v1.2.1 in
|
|
||||||
# git-town.yml; block v1.3.x until upstream tolerates cyclic edges.
|
|
||||||
- dependency-name: "git-town/action"
|
|
||||||
update-types:
|
|
||||||
- "version-update:semver-minor"
|
|
||||||
- "version-update:semver-major"
|
|
||||||
|
|
||||||
- package-ecosystem: "gomod"
|
|
||||||
directories:
|
|
||||||
- "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
open-pull-requests-limit: 15
|
|
||||||
groups:
|
|
||||||
aws-sdk:
|
|
||||||
patterns:
|
|
||||||
- "github.com/aws/aws-sdk-go-v2/*"
|
|
||||||
pion:
|
|
||||||
patterns:
|
|
||||||
- "github.com/pion/*"
|
|
||||||
gorm:
|
|
||||||
patterns:
|
|
||||||
- "gorm.io/*"
|
|
||||||
otel:
|
|
||||||
patterns:
|
|
||||||
- "go.opentelemetry.io/*"
|
|
||||||
testcontainers:
|
|
||||||
patterns:
|
|
||||||
- "github.com/testcontainers/testcontainers-go/*"
|
|
||||||
wireguard:
|
|
||||||
patterns:
|
|
||||||
- "golang.zx2c4.com/wireguard*"
|
|
||||||
105
.github/workflows/check-license-dependencies.yml
vendored
105
.github/workflows/check-license-dependencies.yml
vendored
@@ -2,16 +2,16 @@ name: Check License Dependencies
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [ main ]
|
||||||
paths:
|
paths:
|
||||||
- "go.mod"
|
- 'go.mod'
|
||||||
- "go.sum"
|
- 'go.sum'
|
||||||
- ".github/workflows/check-license-dependencies.yml"
|
- '.github/workflows/check-license-dependencies.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "go.mod"
|
- 'go.mod'
|
||||||
- "go.sum"
|
- 'go.sum'
|
||||||
- ".github/workflows/check-license-dependencies.yml"
|
- '.github/workflows/check-license-dependencies.yml'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-internal-dependencies:
|
check-internal-dependencies:
|
||||||
@@ -19,10 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Check for problematic license dependencies
|
- name: Check for problematic license dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -59,57 +56,55 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: 'go.mod'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install go-licenses
|
- name: Install go-licenses
|
||||||
run: go install github.com/google/go-licenses@v1.6.0
|
run: go install github.com/google/go-licenses@v1.6.0
|
||||||
|
|
||||||
- name: Check for GPL/AGPL licensed dependencies
|
- name: Check for GPL/AGPL licensed dependencies
|
||||||
run: |
|
run: |
|
||||||
echo "Checking for GPL/AGPL/LGPL licensed dependencies..."
|
echo "Checking for GPL/AGPL/LGPL licensed dependencies..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check all Go packages for copyleft licenses, excluding internal netbird packages
|
||||||
|
COPYLEFT_DEPS=$(go-licenses report ./... 2>/dev/null | grep -E 'GPL|AGPL|LGPL' | grep -v 'github.com/netbirdio/netbird/' || true)
|
||||||
|
|
||||||
|
if [ -n "$COPYLEFT_DEPS" ]; then
|
||||||
|
echo "Found copyleft licensed dependencies:"
|
||||||
|
echo "$COPYLEFT_DEPS"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Check all Go packages for copyleft licenses, excluding internal netbird packages
|
# Filter out dependencies that are only pulled in by internal AGPL packages
|
||||||
COPYLEFT_DEPS=$(go-licenses report ./... 2>/dev/null | grep -E 'GPL|AGPL|LGPL' | grep -v 'github.com/netbirdio/netbird/' || true)
|
INCOMPATIBLE=""
|
||||||
|
while IFS=',' read -r package url license; do
|
||||||
|
if echo "$license" | grep -qE 'GPL-[0-9]|AGPL-[0-9]|LGPL-[0-9]'; then
|
||||||
|
# Find ALL packages that import this GPL package using go list
|
||||||
|
IMPORTERS=$(go list -json -deps ./... 2>/dev/null | jq -r "select(.Imports[]? == \"$package\") | .ImportPath")
|
||||||
|
|
||||||
if [ -n "$COPYLEFT_DEPS" ]; then
|
# Check if any importer is NOT in management/signal/relay
|
||||||
echo "Found copyleft licensed dependencies:"
|
BSD_IMPORTER=$(echo "$IMPORTERS" | grep -v "github.com/netbirdio/netbird/\(management\|signal\|relay\|proxy\|combined\|tools/idp-migrate\)" | head -1)
|
||||||
echo "$COPYLEFT_DEPS"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Filter out dependencies that are only pulled in by internal AGPL packages
|
if [ -n "$BSD_IMPORTER" ]; then
|
||||||
INCOMPATIBLE=""
|
echo "❌ $package ($license) is imported by BSD-licensed code: $BSD_IMPORTER"
|
||||||
while IFS=',' read -r package url license; do
|
INCOMPATIBLE="${INCOMPATIBLE}${package},${url},${license}\n"
|
||||||
if echo "$license" | grep -qE 'GPL-[0-9]|AGPL-[0-9]|LGPL-[0-9]'; then
|
else
|
||||||
# Find ALL packages that import this GPL package using go list
|
echo "✓ $package ($license) is only used by internal AGPL packages - OK"
|
||||||
IMPORTERS=$(go list -json -deps ./... 2>/dev/null | jq -r "select(.Imports[]? == \"$package\") | .ImportPath")
|
|
||||||
|
|
||||||
# Check if any importer is NOT in management/signal/relay
|
|
||||||
BSD_IMPORTER=$(echo "$IMPORTERS" | grep -v "github.com/netbirdio/netbird/\(management\|signal\|relay\|proxy\|combined\|tools/idp-migrate\)" | head -1)
|
|
||||||
|
|
||||||
if [ -n "$BSD_IMPORTER" ]; then
|
|
||||||
echo "❌ $package ($license) is imported by BSD-licensed code: $BSD_IMPORTER"
|
|
||||||
INCOMPATIBLE="${INCOMPATIBLE}${package},${url},${license}\n"
|
|
||||||
else
|
|
||||||
echo "✓ $package ($license) is only used by internal AGPL packages - OK"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
done <<< "$COPYLEFT_DEPS"
|
|
||||||
|
|
||||||
if [ -n "$INCOMPATIBLE" ]; then
|
|
||||||
echo ""
|
|
||||||
echo "❌ INCOMPATIBLE licenses found that are used by BSD-licensed code:"
|
|
||||||
echo -e "$INCOMPATIBLE"
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
fi
|
done <<< "$COPYLEFT_DEPS"
|
||||||
|
|
||||||
echo "✅ All external license dependencies are compatible with BSD-3-Clause"
|
if [ -n "$INCOMPATIBLE" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "❌ INCOMPATIBLE licenses found that are used by BSD-licensed code:"
|
||||||
|
echo -e "$INCOMPATIBLE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ All external license dependencies are compatible with BSD-3-Clause"
|
||||||
|
|||||||
2
.github/workflows/docs-ack.yml
vendored
2
.github/workflows/docs-ack.yml
vendored
@@ -83,7 +83,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Verify docs PR exists (and is open or merged)
|
- name: Verify docs PR exists (and is open or merged)
|
||||||
if: steps.validate.outputs.mode == 'added'
|
if: steps.validate.outputs.mode == 'added'
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@v7
|
||||||
id: verify
|
id: verify
|
||||||
with:
|
with:
|
||||||
pr_number: ${{ steps.extract.outputs.pr_number }}
|
pr_number: ${{ steps.extract.outputs.pr_number }}
|
||||||
|
|||||||
5
.github/workflows/forum.yml
vendored
5
.github/workflows/forum.yml
vendored
@@ -8,10 +8,11 @@ jobs:
|
|||||||
post:
|
post:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: roots/discourse-topic-github-release-action@557d74ea05b6cc0c47f555c1d5d28a89d904005b # v1.1.0
|
- uses: roots/discourse-topic-github-release-action@main
|
||||||
with:
|
with:
|
||||||
discourse-api-key: ${{ secrets.DISCOURSE_RELEASES_API_KEY }}
|
discourse-api-key: ${{ secrets.DISCOURSE_RELEASES_API_KEY }}
|
||||||
discourse-base-url: https://forum.netbird.io
|
discourse-base-url: https://forum.netbird.io
|
||||||
discourse-author-username: NetBird
|
discourse-author-username: NetBird
|
||||||
discourse-category: 17
|
discourse-category: 17
|
||||||
discourse-tags: releases
|
discourse-tags:
|
||||||
|
releases
|
||||||
|
|||||||
8
.github/workflows/git-town.yml
vendored
8
.github/workflows/git-town.yml
vendored
@@ -3,7 +3,7 @@ name: Git Town
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- "**"
|
- '**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
git-town:
|
git-town:
|
||||||
@@ -15,9 +15,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@v4
|
||||||
with:
|
- uses: git-town/action@v1.2.1
|
||||||
persist-credentials: false
|
|
||||||
- uses: git-town/action@3d8b878379abb1ee393fb49865a28b4a6c2cd3b0 # v1.2.1
|
|
||||||
with:
|
with:
|
||||||
skip-single-stacks: true
|
skip-single-stacks: true
|
||||||
|
|||||||
9
.github/workflows/golang-test-darwin.yml
vendored
9
.github/workflows/golang-test-darwin.yml
vendored
@@ -16,18 +16,16 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: ~/go/pkg/mod
|
||||||
key: macos-gotest-${{ hashFiles('**/go.sum') }}
|
key: macos-gotest-${{ hashFiles('**/go.sum') }}
|
||||||
@@ -46,3 +44,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -tags=devcert -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 5m -p 1 $(go list ./... | grep -v -e /management -e /signal -e /relay -e /proxy -e /combined)
|
run: NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -tags=devcert -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 5m -p 1 $(go list ./... | grep -v -e /management -e /signal -e /relay -e /proxy -e /combined)
|
||||||
|
|
||||||
|
|||||||
19
.github/workflows/golang-test-freebsd.yml
vendored
19
.github/workflows/golang-test-freebsd.yml
vendored
@@ -15,28 +15,17 @@ jobs:
|
|||||||
name: "Client / Unit"
|
name: "Client / Unit"
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Read Go version from go.mod
|
|
||||||
id: goversion
|
|
||||||
run: echo "version=$(awk '/^go / {print $2}' go.mod)" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Test in FreeBSD
|
- name: Test in FreeBSD
|
||||||
id: test
|
id: test
|
||||||
env:
|
uses: vmactions/freebsd-vm@v1
|
||||||
GO_VERSION: ${{ steps.goversion.outputs.version }}
|
|
||||||
uses: vmactions/freebsd-vm@d1e65811565151536c0c894fff74f06351ed26e6 # v1.4.5
|
|
||||||
with:
|
with:
|
||||||
usesh: true
|
usesh: true
|
||||||
copyback: false
|
copyback: false
|
||||||
release: "15.0"
|
release: "14.2"
|
||||||
envs: "GO_VERSION"
|
|
||||||
prepare: |
|
prepare: |
|
||||||
pkg install -y curl pkgconf xorg
|
pkg install -y curl pkgconf xorg
|
||||||
GO_TARBALL="go${GO_VERSION}.freebsd-amd64.tar.gz"
|
GO_TARBALL="go1.25.3.freebsd-amd64.tar.gz"
|
||||||
GO_URL="https://go.dev/dl/$GO_TARBALL"
|
GO_URL="https://go.dev/dl/$GO_TARBALL"
|
||||||
curl -vLO "$GO_URL"
|
curl -vLO "$GO_URL"
|
||||||
tar -C /usr/local -vxzf "$GO_TARBALL"
|
tar -C /usr/local -vxzf "$GO_TARBALL"
|
||||||
|
|||||||
120
.github/workflows/golang-test-linux.yml
vendored
120
.github/workflows/golang-test-linux.yml
vendored
@@ -18,11 +18,9 @@ jobs:
|
|||||||
management: ${{ steps.filter.outputs.management }}
|
management: ${{ steps.filter.outputs.management }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
- uses: dorny/paths-filter@v3
|
||||||
id: filter
|
id: filter
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
@@ -30,7 +28,7 @@ jobs:
|
|||||||
- 'management/**'
|
- 'management/**'
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
@@ -41,7 +39,7 @@ jobs:
|
|||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@v4
|
||||||
id: cache
|
id: cache
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@@ -115,16 +113,14 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arch: ["386", "amd64"]
|
arch: [ '386','amd64' ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
@@ -135,7 +131,7 @@ jobs:
|
|||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ env.cache }}
|
${{ env.cache }}
|
||||||
@@ -162,16 +158,14 @@ jobs:
|
|||||||
|
|
||||||
test_client_on_docker:
|
test_client_on_docker:
|
||||||
name: "Client (Docker) / Unit"
|
name: "Client (Docker) / Unit"
|
||||||
needs: [build-cache]
|
needs: [ build-cache ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
@@ -183,7 +177,7 @@ jobs:
|
|||||||
echo "modcache_dir=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT
|
echo "modcache_dir=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@v4
|
||||||
id: cache-restore
|
id: cache-restore
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@@ -237,12 +231,10 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
@@ -257,7 +249,7 @@ jobs:
|
|||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ env.cache }}
|
${{ env.cache }}
|
||||||
@@ -285,16 +277,14 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arch: ["386", "amd64"]
|
arch: [ '386','amd64' ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
@@ -308,7 +298,7 @@ jobs:
|
|||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ env.cache }}
|
${{ env.cache }}
|
||||||
@@ -334,16 +324,14 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arch: ["386", "amd64"]
|
arch: [ '386','amd64' ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
@@ -358,7 +346,7 @@ jobs:
|
|||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ env.cache }}
|
${{ env.cache }}
|
||||||
@@ -382,21 +370,19 @@ jobs:
|
|||||||
|
|
||||||
test_management:
|
test_management:
|
||||||
name: "Management / Unit"
|
name: "Management / Unit"
|
||||||
needs: [build-cache]
|
needs: [ build-cache ]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arch: ["amd64"]
|
arch: [ 'amd64' ]
|
||||||
store: ["sqlite", "postgres", "mysql"]
|
store: [ 'sqlite', 'postgres', 'mysql' ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
@@ -407,7 +393,7 @@ jobs:
|
|||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ env.cache }}
|
${{ env.cache }}
|
||||||
@@ -424,7 +410,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Login to Docker hub
|
- name: Login to Docker hub
|
||||||
if: github.event.pull_request && github.event.pull_request.head.repo && github.event.pull_request.head.repo.full_name == '' || github.repository == github.event.pull_request.head.repo.full_name || !github.head_ref
|
if: github.event.pull_request && github.event.pull_request.head.repo && github.event.pull_request.head.repo.full_name == '' || github.repository == github.event.pull_request.head.repo.full_name || !github.head_ref
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
@@ -451,13 +437,13 @@ jobs:
|
|||||||
|
|
||||||
benchmark:
|
benchmark:
|
||||||
name: "Management / Benchmark"
|
name: "Management / Benchmark"
|
||||||
needs: [build-cache]
|
needs: [ build-cache ]
|
||||||
if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }}
|
if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arch: ["amd64"]
|
arch: [ 'amd64' ]
|
||||||
store: ["sqlite", "postgres"]
|
store: [ 'sqlite', 'postgres' ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Create Docker network
|
- name: Create Docker network
|
||||||
@@ -488,12 +474,10 @@ jobs:
|
|||||||
prom/prometheus
|
prom/prometheus
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
@@ -504,7 +488,7 @@ jobs:
|
|||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ env.cache }}
|
${{ env.cache }}
|
||||||
@@ -521,7 +505,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Login to Docker hub
|
- name: Login to Docker hub
|
||||||
if: github.event.pull_request && github.event.pull_request.head.repo && github.event.pull_request.head.repo.full_name == '' || github.repository == github.event.pull_request.head.repo.full_name || !github.head_ref
|
if: github.event.pull_request && github.event.pull_request.head.repo && github.event.pull_request.head.repo.full_name == '' || github.repository == github.event.pull_request.head.repo.full_name || !github.head_ref
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
@@ -545,13 +529,13 @@ jobs:
|
|||||||
|
|
||||||
api_benchmark:
|
api_benchmark:
|
||||||
name: "Management / Benchmark (API)"
|
name: "Management / Benchmark (API)"
|
||||||
needs: [build-cache]
|
needs: [ build-cache ]
|
||||||
if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }}
|
if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arch: ["amd64"]
|
arch: [ 'amd64' ]
|
||||||
store: ["sqlite", "postgres"]
|
store: [ 'sqlite', 'postgres' ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Create Docker network
|
- name: Create Docker network
|
||||||
@@ -582,12 +566,10 @@ jobs:
|
|||||||
prom/prometheus
|
prom/prometheus
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
@@ -598,7 +580,7 @@ jobs:
|
|||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ env.cache }}
|
${{ env.cache }}
|
||||||
@@ -615,7 +597,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Login to Docker hub
|
- name: Login to Docker hub
|
||||||
if: github.event.pull_request && github.event.pull_request.head.repo && github.event.pull_request.head.repo.full_name == '' || github.repository == github.event.pull_request.head.repo.full_name || !github.head_ref
|
if: github.event.pull_request && github.event.pull_request.head.repo && github.event.pull_request.head.repo.full_name == '' || github.repository == github.event.pull_request.head.repo.full_name || !github.head_ref
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
@@ -641,22 +623,20 @@ jobs:
|
|||||||
|
|
||||||
api_integration_test:
|
api_integration_test:
|
||||||
name: "Management / Integration"
|
name: "Management / Integration"
|
||||||
needs: [build-cache]
|
needs: [ build-cache ]
|
||||||
if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }}
|
if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arch: ["amd64"]
|
arch: [ 'amd64' ]
|
||||||
store: ["sqlite", "postgres"]
|
store: [ 'sqlite', 'postgres']
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
@@ -667,7 +647,7 @@ jobs:
|
|||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ env.cache }}
|
${{ env.cache }}
|
||||||
|
|||||||
19
.github/workflows/golang-test-windows.yml
vendored
19
.github/workflows/golang-test-windows.yml
vendored
@@ -18,12 +18,10 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
id: go
|
id: go
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
@@ -35,7 +33,7 @@ jobs:
|
|||||||
echo "modcache=$(go env GOMODCACHE)" >> $env:GITHUB_ENV
|
echo "modcache=$(go env GOMODCACHE)" >> $env:GITHUB_ENV
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ env.cache }}
|
${{ env.cache }}
|
||||||
@@ -46,15 +44,16 @@ jobs:
|
|||||||
${{ runner.os }}-go-
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
- name: Download wintun
|
- name: Download wintun
|
||||||
|
uses: carlosperate/download-file-action@v2
|
||||||
id: download-wintun
|
id: download-wintun
|
||||||
uses: netbirdio/shared-actions/actions/win-download-and-verify@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2
|
|
||||||
with:
|
with:
|
||||||
url: https://pkgs.netbird.io/wintun/wintun-0.14.1.zip
|
file-url: https://pkgs.netbird.io/wintun/wintun-0.14.1.zip
|
||||||
destination: ${{ env.downloadPath }}\wintun.zip
|
file-name: wintun.zip
|
||||||
sha256: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51
|
location: ${{ env.downloadPath }}
|
||||||
|
sha256: '07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51'
|
||||||
|
|
||||||
- name: Decompressing wintun files
|
- name: Decompressing wintun files
|
||||||
run: tar -xvf "${{ steps.download-wintun.outputs.file-path }}" -C ${{ env.downloadPath }}
|
run: tar -zvxf "${{ steps.download-wintun.outputs.file-path }}" -C ${{ env.downloadPath }}
|
||||||
|
|
||||||
- run: mv ${{ env.downloadPath }}/wintun/bin/amd64/wintun.dll 'C:\Windows\System32\'
|
- run: mv ${{ env.downloadPath }}/wintun/bin/amd64/wintun.dll 'C:\Windows\System32\'
|
||||||
|
|
||||||
|
|||||||
14
.github/workflows/golangci-lint.yml
vendored
14
.github/workflows/golangci-lint.yml
vendored
@@ -15,11 +15,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- name: codespell
|
- name: codespell
|
||||||
uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2
|
uses: codespell-project/actions-codespell@v2
|
||||||
with:
|
with:
|
||||||
ignore_words_list: erro,clienta,hastable,iif,groupd,testin,groupe,cros,ans,deriver,te,userA,ede,additionals
|
ignore_words_list: erro,clienta,hastable,iif,groupd,testin,groupe,cros,ans,deriver,te,userA,ede,additionals
|
||||||
skip: go.mod,go.sum,**/proxy/web/**
|
skip: go.mod,go.sum,**/proxy/web/**
|
||||||
@@ -40,15 +38,13 @@ jobs:
|
|||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- name: Check for duplicate constants
|
- name: Check for duplicate constants
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
! awk '/const \(/,/)/{print $0}' management/server/activity/codes.go | grep -o '= [0-9]*' | sort | uniq -d | grep .
|
! awk '/const \(/,/)/{print $0}' management/server/activity/codes.go | grep -o '= [0-9]*' | sort | uniq -d | grep .
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
@@ -56,7 +52,7 @@ jobs:
|
|||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev libpcap-dev
|
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev libpcap-dev
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee #v9.2.1
|
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
skip-cache: true
|
skip-cache: true
|
||||||
|
|||||||
4
.github/workflows/install-script-test.yml
vendored
4
.github/workflows/install-script-test.yml
vendored
@@ -22,9 +22,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: run install script
|
- name: run install script
|
||||||
env:
|
env:
|
||||||
|
|||||||
18
.github/workflows/mobile-build-validation.yml
vendored
18
.github/workflows/mobile-build-validation.yml
vendored
@@ -16,25 +16,23 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
|
uses: android-actions/setup-android@v3
|
||||||
with:
|
with:
|
||||||
cmdline-tools-version: 8512546
|
cmdline-tools-version: 8512546
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: "11"
|
java-version: "11"
|
||||||
distribution: "adopt"
|
distribution: "adopt"
|
||||||
- name: NDK Cache
|
- name: NDK Cache
|
||||||
id: ndk-cache
|
id: ndk-cache
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: /usr/local/lib/android/sdk/ndk
|
path: /usr/local/lib/android/sdk/ndk
|
||||||
key: ndk-cache-23.1.7779620
|
key: ndk-cache-23.1.7779620
|
||||||
@@ -54,11 +52,9 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
- name: install gomobile
|
- name: install gomobile
|
||||||
|
|||||||
2
.github/workflows/pr-title-check.yml
vendored
2
.github/workflows/pr-title-check.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Validate PR title prefix
|
- name: Validate PR title prefix
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const title = context.payload.pull_request.title;
|
const title = context.payload.pull_request.title;
|
||||||
|
|||||||
138
.github/workflows/proto-version-check.yml
vendored
138
.github/workflows/proto-version-check.yml
vendored
@@ -3,92 +3,74 @@ name: Proto Version Check
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
|
- "**/*.proto"
|
||||||
- "**/*.pb.go"
|
- "**/*.pb.go"
|
||||||
|
- "**/generate.sh"
|
||||||
|
- "proto-tools.env"
|
||||||
|
- ".github/workflows/proto-version-check.yml"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-proto-versions:
|
regenerate-and-diff:
|
||||||
|
name: Regenerate proto and verify no drift
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check for proto tool version changes
|
- name: Checkout
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Load pinned proto toolchain versions
|
||||||
|
run: |
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. ./proto-tools.env
|
||||||
|
{
|
||||||
|
echo "PROTOC_VERSION=${PROTOC_VERSION}"
|
||||||
|
echo "PROTOC_GEN_GO_VERSION=${PROTOC_GEN_GO_VERSION}"
|
||||||
|
echo "PROTOC_GEN_GO_GRPC_VERSION=${PROTOC_GEN_GO_GRPC_VERSION}"
|
||||||
|
} >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
script: |
|
go-version-file: go.mod
|
||||||
const files = await github.paginate(github.rest.pulls.listFiles, {
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
pull_number: context.issue.number,
|
|
||||||
per_page: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
const modifiedPbFiles = files.filter(
|
- name: Setup protoc
|
||||||
f => f.filename.endsWith('.pb.go') && f.status === 'modified'
|
uses: arduino/setup-protoc@f4d5893b897028ff5739576ea0409746887fa536 # v3.0.0
|
||||||
);
|
with:
|
||||||
if (modifiedPbFiles.length === 0) {
|
version: ${{ env.PROTOC_VERSION }}
|
||||||
console.log('No modified .pb.go files to check');
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const versionPattern = /^\s*\/\/\s+protoc(?:-gen-go)?\s+v[\d.]+/;
|
- name: Install protoc plugins
|
||||||
const baseSha = context.payload.pull_request.base.sha;
|
run: |
|
||||||
const headSha = context.payload.pull_request.head.sha;
|
go install "google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VERSION}"
|
||||||
|
go install "google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION}"
|
||||||
|
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
async function getVersionHeader(path, ref) {
|
- name: Verify protoc version matches pin
|
||||||
try {
|
run: |
|
||||||
const res = await github.rest.repos.getContent({
|
actual=$(protoc --version | awk '{print $2}')
|
||||||
owner: context.repo.owner,
|
if [[ "$actual" != "$PROTOC_VERSION" ]]; then
|
||||||
repo: context.repo.repo,
|
echo "::error::protoc $actual does not match pinned $PROTOC_VERSION"
|
||||||
path,
|
exit 1
|
||||||
ref,
|
fi
|
||||||
});
|
|
||||||
if (!res.data.content) {
|
|
||||||
return { ok: false, reason: 'no inline content (file too large)' };
|
|
||||||
}
|
|
||||||
const content = Buffer.from(res.data.content, 'base64').toString('utf8');
|
|
||||||
const lines = content
|
|
||||||
.split('\n')
|
|
||||||
.slice(0, 20)
|
|
||||||
.filter(line => versionPattern.test(line));
|
|
||||||
return { ok: true, lines };
|
|
||||||
} catch (e) {
|
|
||||||
return { ok: false, reason: e.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const violations = [];
|
- name: Regenerate all proto bindings
|
||||||
for (const file of modifiedPbFiles) {
|
run: |
|
||||||
const [base, head] = await Promise.all([
|
set -euo pipefail
|
||||||
getVersionHeader(file.filename, baseSha),
|
for script in \
|
||||||
getVersionHeader(file.filename, headSha),
|
client/proto/generate.sh \
|
||||||
]);
|
shared/signal/proto/generate.sh \
|
||||||
if (!base.ok || !head.ok) {
|
shared/management/proto/generate.sh \
|
||||||
core.warning(
|
flow/proto/generate.sh \
|
||||||
`Skipping ${file.filename}: base=${base.ok ? 'ok' : base.reason}, head=${head.ok ? 'ok' : head.reason}`
|
encryption/testprotos/generate.sh; do
|
||||||
);
|
echo "::group::$script"
|
||||||
continue;
|
bash "$script"
|
||||||
}
|
echo "::endgroup::"
|
||||||
if (base.lines.join('\n') !== head.lines.join('\n')) {
|
done
|
||||||
violations.push({
|
|
||||||
file: file.filename,
|
|
||||||
base: base.lines,
|
|
||||||
head: head.lines,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (violations.length > 0) {
|
- name: Fail if regeneration changed any tracked or untracked file
|
||||||
const details = violations.map(v =>
|
run: |
|
||||||
`${v.file}:\n` +
|
if [[ -n "$(git status --porcelain --untracked-files=all)" ]]; then
|
||||||
` base:\n${v.base.map(l => ' ' + l).join('\n') || ' (none)'}\n` +
|
echo "::error::Generated proto files drift from .proto sources or pinned tool versions."
|
||||||
` head:\n${v.head.map(l => ' ' + l).join('\n') || ' (none)'}`
|
echo "Run the generate.sh scripts locally with the toolchain in proto-tools.env and commit the result."
|
||||||
).join('\n\n');
|
git status --short
|
||||||
|
exit 1
|
||||||
core.setFailed(
|
fi
|
||||||
`Proto version strings changed in generated files.\n` +
|
|
||||||
`This usually means the wrong protoc or protoc-gen-go version was used.\n` +
|
|
||||||
`Regenerate with the matching tool versions.\n\n` +
|
|
||||||
details
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('No proto version string changes detected');
|
|
||||||
|
|||||||
164
.github/workflows/release.yml
vendored
164
.github/workflows/release.yml
vendored
@@ -9,7 +9,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
SIGN_PIPE_VER: "v0.1.5"
|
SIGN_PIPE_VER: "v0.1.4"
|
||||||
GORELEASER_VER: "v2.14.3"
|
GORELEASER_VER: "v2.14.3"
|
||||||
PRODUCT_NAME: "NetBird"
|
PRODUCT_NAME: "NetBird"
|
||||||
COPYRIGHT: "NetBird GmbH"
|
COPYRIGHT: "NetBird GmbH"
|
||||||
@@ -24,9 +24,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Generate FreeBSD port diff
|
- name: Generate FreeBSD port diff
|
||||||
run: bash release_files/freebsd-port-diff.sh
|
run: bash release_files/freebsd-port-diff.sh
|
||||||
@@ -53,26 +51,19 @@ jobs:
|
|||||||
echo "Generated files for version: $VERSION"
|
echo "Generated files for version: $VERSION"
|
||||||
cat netbird-*.diff
|
cat netbird-*.diff
|
||||||
|
|
||||||
- name: Read Go version from go.mod
|
|
||||||
id: goversion
|
|
||||||
run: echo "version=$(awk '/^go / {print $2}' go.mod)" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Test FreeBSD port
|
- name: Test FreeBSD port
|
||||||
if: steps.check_diff.outputs.diff_exists == 'true'
|
if: steps.check_diff.outputs.diff_exists == 'true'
|
||||||
env:
|
uses: vmactions/freebsd-vm@v1
|
||||||
GO_VERSION: ${{ steps.goversion.outputs.version }}
|
|
||||||
uses: vmactions/freebsd-vm@d1e65811565151536c0c894fff74f06351ed26e6 # v1.4.5
|
|
||||||
with:
|
with:
|
||||||
usesh: true
|
usesh: true
|
||||||
copyback: false
|
copyback: false
|
||||||
release: "15.0"
|
release: "15.0"
|
||||||
envs: "GO_VERSION"
|
|
||||||
prepare: |
|
prepare: |
|
||||||
# Install required packages
|
# Install required packages
|
||||||
pkg install -y git curl portlint
|
pkg install -y git curl portlint go
|
||||||
|
|
||||||
# Install Go for building
|
# Install Go for building
|
||||||
GO_TARBALL="go${GO_VERSION}.freebsd-amd64.tar.gz"
|
GO_TARBALL="go1.25.5.freebsd-amd64.tar.gz"
|
||||||
GO_URL="https://go.dev/dl/$GO_TARBALL"
|
GO_URL="https://go.dev/dl/$GO_TARBALL"
|
||||||
curl -LO "$GO_URL"
|
curl -LO "$GO_URL"
|
||||||
tar -C /usr/local -xzf "$GO_TARBALL"
|
tar -C /usr/local -xzf "$GO_TARBALL"
|
||||||
@@ -114,7 +105,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload FreeBSD port files
|
- name: Upload FreeBSD port files
|
||||||
if: steps.check_diff.outputs.diff_exists == 'true'
|
if: steps.check_diff.outputs.diff_exists == 'true'
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: freebsd-port-files
|
name: freebsd-port-files
|
||||||
path: |
|
path: |
|
||||||
@@ -133,25 +124,26 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
flags: ""
|
flags: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # It is required for GoReleaser to work properly
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Parse semver string
|
- name: Parse semver string
|
||||||
id: semver_parser
|
id: semver_parser
|
||||||
uses: netbirdio/shared-actions/actions/parse-semver@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2
|
uses: booxmedialtd/ws-action-parse-semver@v1
|
||||||
|
with:
|
||||||
|
input_string: ${{ (startsWith(github.ref, 'refs/tags/v') && github.ref) || 'refs/tags/v0.0.0' }}
|
||||||
|
version_extractor_regex: '\/v(.*)$'
|
||||||
|
|
||||||
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||||
run: echo "flags=--snapshot" >> $GITHUB_ENV
|
run: echo "flags=--snapshot" >> $GITHUB_ENV
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # It is required for GoReleaser to work properly
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/pkg/mod
|
~/go/pkg/mod
|
||||||
@@ -164,18 +156,18 @@ jobs:
|
|||||||
- name: check git status
|
- name: check git status
|
||||||
run: git --no-pager diff --exit-code
|
run: git --no-pager diff --exit-code
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a #v4.0.0
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd #v4.0.0
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to Docker hub
|
- name: Login to Docker hub
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -199,7 +191,7 @@ jobs:
|
|||||||
run: goversioninfo -arm -64 -icon client/ui/assets/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_arm64.syso
|
run: goversioninfo -arm -64 -icon client/ui/assets/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_arm64.syso
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
id: goreleaser
|
id: goreleaser
|
||||||
uses: goreleaser/goreleaser-action@4c6ab561adb47e50c45ef534e2155934e91c40c1 # v7.2.0
|
uses: goreleaser/goreleaser-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.GORELEASER_VER }}
|
version: ${{ env.GORELEASER_VER }}
|
||||||
args: release --clean ${{ env.flags }}
|
args: release --clean ${{ env.flags }}
|
||||||
@@ -290,28 +282,28 @@ jobs:
|
|||||||
} >> "$GITHUB_OUTPUT"
|
} >> "$GITHUB_OUTPUT"
|
||||||
- name: upload non tags for debug purposes
|
- name: upload non tags for debug purposes
|
||||||
id: upload_release
|
id: upload_release
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: release
|
name: release
|
||||||
path: dist/
|
path: dist/
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
- name: upload linux packages
|
- name: upload linux packages
|
||||||
id: upload_linux_packages
|
id: upload_linux_packages
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: linux-packages
|
name: linux-packages
|
||||||
path: dist/netbird_linux**
|
path: dist/netbird_linux**
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
- name: upload windows packages
|
- name: upload windows packages
|
||||||
id: upload_windows_packages
|
id: upload_windows_packages
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-packages
|
name: windows-packages
|
||||||
path: dist/netbird_windows**
|
path: dist/netbird_windows**
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
- name: upload macos packages
|
- name: upload macos packages
|
||||||
id: upload_macos_packages
|
id: upload_macos_packages
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macos-packages
|
name: macos-packages
|
||||||
path: dist/netbird_darwin**
|
path: dist/netbird_darwin**
|
||||||
@@ -322,26 +314,27 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
release_ui_artifact_url: ${{ steps.upload_release_ui.outputs.artifact-url }}
|
release_ui_artifact_url: ${{ steps.upload_release_ui.outputs.artifact-url }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # It is required for GoReleaser to work properly
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Parse semver string
|
- name: Parse semver string
|
||||||
id: semver_parser
|
id: semver_parser
|
||||||
uses: netbirdio/shared-actions/actions/parse-semver@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2
|
uses: booxmedialtd/ws-action-parse-semver@v1
|
||||||
|
with:
|
||||||
|
input_string: ${{ (startsWith(github.ref, 'refs/tags/v') && github.ref) || 'refs/tags/v0.0.0' }}
|
||||||
|
version_extractor_regex: '\/v(.*)$'
|
||||||
|
|
||||||
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||||
run: echo "flags=--snapshot" >> $GITHUB_ENV
|
run: echo "flags=--snapshot" >> $GITHUB_ENV
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # It is required for GoReleaser to work properly
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/pkg/mod
|
~/go/pkg/mod
|
||||||
@@ -382,7 +375,7 @@ jobs:
|
|||||||
run: goversioninfo -arm -64 -icon client/ui/assets/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_arm64.syso
|
run: goversioninfo -arm -64 -icon client/ui/assets/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_arm64.syso
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@4c6ab561adb47e50c45ef534e2155934e91c40c1 # v7.2.0
|
uses: goreleaser/goreleaser-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.GORELEASER_VER }}
|
version: ${{ env.GORELEASER_VER }}
|
||||||
args: release --config .goreleaser_ui.yaml --clean ${{ env.flags }}
|
args: release --config .goreleaser_ui.yaml --clean ${{ env.flags }}
|
||||||
@@ -411,7 +404,7 @@ jobs:
|
|||||||
run: rm -f /tmp/gpg-rpm-signing-key.asc
|
run: rm -f /tmp/gpg-rpm-signing-key.asc
|
||||||
- name: upload non tags for debug purposes
|
- name: upload non tags for debug purposes
|
||||||
id: upload_release_ui
|
id: upload_release_ui
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: release-ui
|
name: release-ui
|
||||||
path: dist/
|
path: dist/
|
||||||
@@ -425,17 +418,16 @@ jobs:
|
|||||||
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||||
run: echo "flags=--snapshot" >> $GITHUB_ENV
|
run: echo "flags=--snapshot" >> $GITHUB_ENV
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # It is required for GoReleaser to work properly
|
fetch-depth: 0 # It is required for GoReleaser to work properly
|
||||||
persist-credentials: false
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
cache: false
|
cache: false
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/pkg/mod
|
~/go/pkg/mod
|
||||||
@@ -449,7 +441,7 @@ jobs:
|
|||||||
run: git --no-pager diff --exit-code
|
run: git --no-pager diff --exit-code
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
id: goreleaser
|
id: goreleaser
|
||||||
uses: goreleaser/goreleaser-action@4c6ab561adb47e50c45ef534e2155934e91c40c1 # v7.2.0
|
uses: goreleaser/goreleaser-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ env.GORELEASER_VER }}
|
version: ${{ env.GORELEASER_VER }}
|
||||||
args: release --config .goreleaser_ui_darwin.yaml --clean ${{ env.flags }}
|
args: release --config .goreleaser_ui_darwin.yaml --clean ${{ env.flags }}
|
||||||
@@ -457,7 +449,7 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: upload non tags for debug purposes
|
- name: upload non tags for debug purposes
|
||||||
id: upload_release_ui_darwin
|
id: upload_release_ui_darwin
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: release-ui-darwin
|
name: release-ui-darwin
|
||||||
path: dist/
|
path: dist/
|
||||||
@@ -482,26 +474,27 @@ jobs:
|
|||||||
PackageWorkdir: netbird_windows_${{ matrix.arch }}
|
PackageWorkdir: netbird_windows_${{ matrix.arch }}
|
||||||
downloadPath: '${{ github.workspace }}\temp'
|
downloadPath: '${{ github.workspace }}\temp'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Parse semver string
|
- name: Parse semver string
|
||||||
id: semver_parser
|
id: semver_parser
|
||||||
uses: netbirdio/shared-actions/actions/parse-semver@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2
|
uses: booxmedialtd/ws-action-parse-semver@v1
|
||||||
|
with:
|
||||||
|
input_string: ${{ (startsWith(github.ref, 'refs/tags/v') && github.ref) || 'refs/tags/v0.0.0' }}
|
||||||
|
version_extractor_regex: '\/v(.*)$'
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Add 7-Zip to PATH
|
- name: Add 7-Zip to PATH
|
||||||
run: echo "C:\Program Files\7-Zip" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
run: echo "C:\Program Files\7-Zip" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
|
||||||
- name: Download release artifacts
|
- name: Download release artifacts
|
||||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.1
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: release
|
name: release
|
||||||
path: release
|
path: release
|
||||||
|
|
||||||
- name: Download UI release artifacts
|
- name: Download UI release artifacts
|
||||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.1
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: release-ui
|
name: release-ui
|
||||||
path: release-ui
|
path: release-ui
|
||||||
@@ -521,27 +514,29 @@ jobs:
|
|||||||
Get-ChildItem $workdir
|
Get-ChildItem $workdir
|
||||||
|
|
||||||
- name: Download wintun
|
- name: Download wintun
|
||||||
|
uses: carlosperate/download-file-action@v2
|
||||||
id: download-wintun
|
id: download-wintun
|
||||||
uses: netbirdio/shared-actions/actions/win-download-and-verify@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2
|
|
||||||
with:
|
with:
|
||||||
url: https://pkgs.netbird.io/wintun/wintun-0.14.1.zip
|
file-url: https://pkgs.netbird.io/wintun/wintun-0.14.1.zip
|
||||||
destination: ${{ env.downloadPath }}\wintun.zip
|
file-name: wintun.zip
|
||||||
sha256: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51
|
location: ${{ env.downloadPath }}
|
||||||
|
sha256: '07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51'
|
||||||
|
|
||||||
- name: Decompress wintun files
|
- name: Decompress wintun files
|
||||||
run: tar -xvf "${{ env.downloadPath }}\wintun.zip" -C ${{ env.downloadPath }}
|
run: tar -zvxf "${{ steps.download-wintun.outputs.file-path }}" -C ${{ env.downloadPath }}
|
||||||
|
|
||||||
- name: Move wintun.dll into dist
|
- name: Move wintun.dll into dist
|
||||||
run: mv ${{ env.downloadPath }}\wintun\bin\${{ matrix.wintun_arch }}\wintun.dll ${{ github.workspace }}\dist\${{ env.PackageWorkdir }}\
|
run: mv ${{ env.downloadPath }}\wintun\bin\${{ matrix.wintun_arch }}\wintun.dll ${{ github.workspace }}\dist\${{ env.PackageWorkdir }}\
|
||||||
|
|
||||||
- name: Download Mesa3D (amd64 only)
|
- name: Download Mesa3D (amd64 only)
|
||||||
|
uses: carlosperate/download-file-action@v2
|
||||||
id: download-mesa3d
|
id: download-mesa3d
|
||||||
if: matrix.arch == 'amd64'
|
if: matrix.arch == 'amd64'
|
||||||
uses: netbirdio/shared-actions/actions/win-download-and-verify@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2
|
|
||||||
with:
|
with:
|
||||||
url: https://pkgs.netbird.io/mesa3d/MesaForWindows-x64-20.1.8.7z
|
file-url: https://downloads.fdossena.com/Projects/Mesa3D/Builds/MesaForWindows-x64-20.1.8.7z
|
||||||
destination: ${{ env.downloadPath }}\mesa3d.7z
|
file-name: mesa3d.7z
|
||||||
sha256: 71c7cb64ec229a1d6b8d62fa08e1889ed2bd17c0eeede8689daf0f25cb31d6b9
|
location: ${{ env.downloadPath }}
|
||||||
|
sha256: '71c7cb64ec229a1d6b8d62fa08e1889ed2bd17c0eeede8689daf0f25cb31d6b9'
|
||||||
|
|
||||||
- name: Extract Mesa3D driver (amd64 only)
|
- name: Extract Mesa3D driver (amd64 only)
|
||||||
if: matrix.arch == 'amd64'
|
if: matrix.arch == 'amd64'
|
||||||
@@ -552,38 +547,35 @@ jobs:
|
|||||||
run: mv ${{ env.downloadPath }}\opengl32.dll ${{ github.workspace }}\dist\${{ env.PackageWorkdir }}\
|
run: mv ${{ env.downloadPath }}\opengl32.dll ${{ github.workspace }}\dist\${{ env.PackageWorkdir }}\
|
||||||
|
|
||||||
- name: Download EnVar plugin for NSIS
|
- name: Download EnVar plugin for NSIS
|
||||||
uses: netbirdio/shared-actions/actions/win-download-and-verify@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2
|
uses: carlosperate/download-file-action@v2
|
||||||
with:
|
with:
|
||||||
url: https://pkgs.netbird.io/nsis/EnVar_plugin.zip
|
file-url: https://nsis.sourceforge.io/mediawiki/images/7/7f/EnVar_plugin.zip
|
||||||
destination: ${{ github.workspace }}\envar_plugin.zip
|
file-name: envar_plugin.zip
|
||||||
sha256: e9aa92de351345ed82795251d838f1ae9041ba35af9d381a5780c7843b01f56a
|
location: ${{ github.workspace }}
|
||||||
|
|
||||||
- name: Extract EnVar plugin
|
- name: Extract EnVar plugin
|
||||||
run: 7z x -o"${{ github.workspace }}/NSIS_Plugins" "${{ github.workspace }}/envar_plugin.zip"
|
run: 7z x -o"${{ github.workspace }}/NSIS_Plugins" "${{ github.workspace }}/envar_plugin.zip"
|
||||||
|
|
||||||
- name: Download ShellExecAsUser plugin for NSIS (amd64 only)
|
- name: Download ShellExecAsUser plugin for NSIS (amd64 only)
|
||||||
|
uses: carlosperate/download-file-action@v2
|
||||||
if: matrix.arch == 'amd64'
|
if: matrix.arch == 'amd64'
|
||||||
uses: netbirdio/shared-actions/actions/win-download-and-verify@be5df6047383da2236e02243cceb857d8567c27e # v0.0.2
|
|
||||||
with:
|
with:
|
||||||
url: https://pkgs.netbird.io/nsis/ShellExecAsUser_amd64-Unicode.7z
|
file-url: https://nsis.sourceforge.io/mediawiki/images/6/68/ShellExecAsUser_amd64-Unicode.7z
|
||||||
destination: ${{ github.workspace }}\ShellExecAsUser_amd64-Unicode.7z
|
file-name: ShellExecAsUser_amd64-Unicode.7z
|
||||||
sha256: 0a55ea25c7330a92cec028eda8afcaf1b1a7092e0dfb77c21c8f654564b4ff9d
|
location: ${{ github.workspace }}
|
||||||
|
|
||||||
- name: Extract ShellExecAsUser plugin (amd64 only)
|
- name: Extract ShellExecAsUser plugin (amd64 only)
|
||||||
if: matrix.arch == 'amd64'
|
if: matrix.arch == 'amd64'
|
||||||
run: 7z x -o"${{ github.workspace }}/NSIS_Plugins" "${{ github.workspace }}/ShellExecAsUser_amd64-Unicode.7z"
|
run: 7z x -o"${{ github.workspace }}/NSIS_Plugins" "${{ github.workspace }}/ShellExecAsUser_amd64-Unicode.7z"
|
||||||
|
|
||||||
- name: Build NSIS installer
|
- name: Build NSIS installer
|
||||||
shell: pwsh
|
uses: joncloud/makensis-action@v3.3
|
||||||
|
with:
|
||||||
|
additional-plugin-paths: ${{ github.workspace }}/NSIS_Plugins/Plugins
|
||||||
|
script-file: client/installer.nsis
|
||||||
|
arguments: "/V4 /DARCH=${{ matrix.arch }}"
|
||||||
env:
|
env:
|
||||||
APPVER: ${{ steps.semver_parser.outputs.major }}.${{ steps.semver_parser.outputs.minor }}.${{ steps.semver_parser.outputs.patch }}.${{ github.run_id }}
|
APPVER: ${{ steps.semver_parser.outputs.major }}.${{ steps.semver_parser.outputs.minor }}.${{ steps.semver_parser.outputs.patch }}.${{ github.run_id }}
|
||||||
run: |
|
|
||||||
$nsisPluginDir = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode"
|
|
||||||
$srcPlugins = "${{ github.workspace }}\NSIS_Plugins\Plugins"
|
|
||||||
Get-ChildItem -Path $srcPlugins -Recurse -Filter *.dll |
|
|
||||||
Copy-Item -Destination $nsisPluginDir -Force
|
|
||||||
& "C:\Program Files (x86)\NSIS\makensis.exe" /V4 "/DARCH=${{ matrix.arch }}" client\installer.nsis
|
|
||||||
if ($LASTEXITCODE -ne 0) { throw "makensis failed with exit code $LASTEXITCODE" }
|
|
||||||
|
|
||||||
- name: Rename NSIS installer
|
- name: Rename NSIS installer
|
||||||
run: mv netbird-installer.exe netbird_installer_test_windows_${{ matrix.arch }}.exe
|
run: mv netbird-installer.exe netbird_installer_test_windows_${{ matrix.arch }}.exe
|
||||||
@@ -600,7 +592,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload installer artifacts
|
- name: Upload installer artifacts
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-installer-test-${{ matrix.arch }}
|
name: windows-installer-test-${{ matrix.arch }}
|
||||||
path: |
|
path: |
|
||||||
@@ -619,7 +611,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Create or update PR comment
|
- name: Create or update PR comment
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@v7
|
||||||
env:
|
env:
|
||||||
RELEASE_RESULT: ${{ needs.release.result }}
|
RELEASE_RESULT: ${{ needs.release.result }}
|
||||||
RELEASE_UI_RESULT: ${{ needs.release_ui.result }}
|
RELEASE_UI_RESULT: ${{ needs.release_ui.result }}
|
||||||
@@ -711,7 +703,7 @@ jobs:
|
|||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger binaries sign pipelines
|
- name: Trigger binaries sign pipelines
|
||||||
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1.3.2
|
uses: benc-uk/workflow-dispatch@v1
|
||||||
with:
|
with:
|
||||||
workflow: Sign bin and installer
|
workflow: Sign bin and installer
|
||||||
repo: netbirdio/sign-pipelines
|
repo: netbirdio/sign-pipelines
|
||||||
|
|||||||
2
.github/workflows/sync-main.yml
vendored
2
.github/workflows/sync-main.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger main branch sync
|
- name: Trigger main branch sync
|
||||||
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1.3.2
|
uses: benc-uk/workflow-dispatch@v1
|
||||||
with:
|
with:
|
||||||
workflow: sync-main.yml
|
workflow: sync-main.yml
|
||||||
repo: ${{ secrets.UPSTREAM_REPO }}
|
repo: ${{ secrets.UPSTREAM_REPO }}
|
||||||
|
|||||||
8
.github/workflows/sync-tag.yml
vendored
8
.github/workflows/sync-tag.yml
vendored
@@ -3,7 +3,7 @@ name: sync tag
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- 'v*'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger release tag sync
|
- name: Trigger release tag sync
|
||||||
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1.3.2
|
uses: benc-uk/workflow-dispatch@v1
|
||||||
with:
|
with:
|
||||||
workflow: sync-tag.yml
|
workflow: sync-tag.yml
|
||||||
ref: main
|
ref: main
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
if: github.event.created && !github.event.deleted && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')
|
if: github.event.created && !github.event.deleted && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger android-client submodule bump
|
- name: Trigger android-client submodule bump
|
||||||
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1.3.2
|
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4 # v1.3.1
|
||||||
with:
|
with:
|
||||||
workflow: bump-netbird.yml
|
workflow: bump-netbird.yml
|
||||||
ref: main
|
ref: main
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
if: github.event.created && !github.event.deleted && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')
|
if: github.event.created && !github.event.deleted && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger ios-client submodule bump
|
- name: Trigger ios-client submodule bump
|
||||||
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1.3.2
|
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4 # v1.3.1
|
||||||
with:
|
with:
|
||||||
workflow: bump-netbird.yml
|
workflow: bump-netbird.yml
|
||||||
ref: main
|
ref: main
|
||||||
|
|||||||
26
.github/workflows/test-infrastructure-files.yml
vendored
26
.github/workflows/test-infrastructure-files.yml
vendored
@@ -6,10 +6,10 @@ on:
|
|||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "infrastructure_files/**"
|
- 'infrastructure_files/**'
|
||||||
- ".github/workflows/test-infrastructure-files.yml"
|
- '.github/workflows/test-infrastructure-files.yml'
|
||||||
- "management/cmd/**"
|
- 'management/cmd/**'
|
||||||
- "signal/cmd/**"
|
- 'signal/cmd/**'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
store: ["sqlite", "postgres", "mysql"]
|
store: [ 'sqlite', 'postgres', 'mysql' ]
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: ${{ (matrix.store == 'postgres') && 'postgres' || '' }}
|
image: ${{ (matrix.store == 'postgres') && 'postgres' || '' }}
|
||||||
@@ -68,17 +68,15 @@ jobs:
|
|||||||
run: sudo apt-get install -y curl
|
run: sudo apt-get install -y curl
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: ~/go/pkg/mod
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
@@ -141,8 +139,8 @@ jobs:
|
|||||||
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
||||||
CI_NETBIRD_SIGNAL_PORT: 12345
|
CI_NETBIRD_SIGNAL_PORT: 12345
|
||||||
CI_NETBIRD_STORE_CONFIG_ENGINE: ${{ matrix.store }}
|
CI_NETBIRD_STORE_CONFIG_ENGINE: ${{ matrix.store }}
|
||||||
NETBIRD_STORE_ENGINE_POSTGRES_DSN: "${{ env.NETBIRD_STORE_ENGINE_POSTGRES_DSN }}$"
|
NETBIRD_STORE_ENGINE_POSTGRES_DSN: '${{ env.NETBIRD_STORE_ENGINE_POSTGRES_DSN }}$'
|
||||||
NETBIRD_STORE_ENGINE_MYSQL_DSN: "${{ env.NETBIRD_STORE_ENGINE_MYSQL_DSN }}$"
|
NETBIRD_STORE_ENGINE_MYSQL_DSN: '${{ env.NETBIRD_STORE_ENGINE_MYSQL_DSN }}$'
|
||||||
CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH: false
|
CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH: false
|
||||||
CI_NETBIRD_TURN_EXTERNAL_IP: "1.2.3.4"
|
CI_NETBIRD_TURN_EXTERNAL_IP: "1.2.3.4"
|
||||||
CI_NETBIRD_MGMT_DISABLE_DEFAULT_POLICY: false
|
CI_NETBIRD_MGMT_DISABLE_DEFAULT_POLICY: false
|
||||||
@@ -256,9 +254,7 @@ jobs:
|
|||||||
run: sudo apt-get install -y jq
|
run: sudo apt-get install -y jq
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: run script with Zitadel PostgreSQL
|
- name: run script with Zitadel PostgreSQL
|
||||||
run: NETBIRD_DOMAIN=use-ip bash -x infrastructure_files/getting-started-with-zitadel.sh
|
run: NETBIRD_DOMAIN=use-ip bash -x infrastructure_files/getting-started-with-zitadel.sh
|
||||||
|
|||||||
6
.github/workflows/update-docs.yml
vendored
6
.github/workflows/update-docs.yml
vendored
@@ -3,9 +3,9 @@ name: update docs
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- 'v*'
|
||||||
paths:
|
paths:
|
||||||
- "shared/management/http/api/openapi.yml"
|
- 'shared/management/http/api/openapi.yml'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
trigger_docs_api_update:
|
trigger_docs_api_update:
|
||||||
@@ -13,7 +13,7 @@ jobs:
|
|||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger API pages generation
|
- name: Trigger API pages generation
|
||||||
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1.3.2
|
uses: benc-uk/workflow-dispatch@v1
|
||||||
with:
|
with:
|
||||||
workflow: generate api pages
|
workflow: generate api pages
|
||||||
repo: netbirdio/docs
|
repo: netbirdio/docs
|
||||||
|
|||||||
15
.github/workflows/wasm-build-validation.yml
vendored
15
.github/workflows/wasm-build-validation.yml
vendored
@@ -19,17 +19,15 @@ jobs:
|
|||||||
GOARCH: wasm
|
GOARCH: wasm
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev libpcap-dev
|
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev libpcap-dev
|
||||||
- name: Install golangci-lint
|
- name: Install golangci-lint
|
||||||
uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee #v9.2.1
|
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
install-mode: binary
|
install-mode: binary
|
||||||
@@ -44,11 +42,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
- name: Build Wasm client
|
- name: Build Wasm client
|
||||||
@@ -69,3 +65,4 @@ jobs:
|
|||||||
echo "Wasm binary size (${SIZE_MB}MB) exceeds 56MB limit!"
|
echo "Wasm binary size (${SIZE_MB}MB) exceeds 56MB limit!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator/validator"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
nbcache "github.com/netbirdio/netbird/management/server/cache"
|
nbcache "github.com/netbirdio/netbird/management/server/cache"
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ func startManagement(t *testing.T, config *config.Config, testFile string) (*grp
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
iv, _ := validator.NewIntegratedValidator(ctx, peersmanager, settingsManagerMock, eventStore, cacheStore)
|
iv, _ := integrations.NewIntegratedValidator(ctx, peersmanager, settingsManagerMock, eventStore, cacheStore)
|
||||||
|
|
||||||
metrics, err := telemetry.NewDefaultAppMetrics(ctx)
|
metrics, err := telemetry.NewDefaultAppMetrics(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
|
||||||
wgnetstack "golang.zx2c4.com/wireguard/tun/netstack"
|
wgnetstack "golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
"github.com/netbirdio/netbird/client/iface"
|
||||||
@@ -85,12 +84,6 @@ type Options struct {
|
|||||||
DisableIPv6 bool
|
DisableIPv6 bool
|
||||||
// BlockInbound blocks all inbound connections from peers
|
// BlockInbound blocks all inbound connections from peers
|
||||||
BlockInbound bool
|
BlockInbound bool
|
||||||
// BlockLANAccess blocks the embedded peer from reaching the host's
|
|
||||||
// LAN (RFC 1918, link-local, loopback) when it's used as a routing
|
|
||||||
// peer. Mirrors profilemanager.ConfigInput.BlockLANAccess. Useful
|
|
||||||
// when the embedded client must never act as a stepping stone into
|
|
||||||
// the host's local network (e.g. the proxy's overlay peer).
|
|
||||||
BlockLANAccess bool
|
|
||||||
// WireguardPort is the port for the tunnel interface. Use 0 for a random port.
|
// WireguardPort is the port for the tunnel interface. Use 0 for a random port.
|
||||||
WireguardPort *int
|
WireguardPort *int
|
||||||
// MTU is the MTU for the tunnel interface.
|
// MTU is the MTU for the tunnel interface.
|
||||||
@@ -101,26 +94,6 @@ type Options struct {
|
|||||||
MTU *uint16
|
MTU *uint16
|
||||||
// DNSLabels defines additional DNS labels configured in the peer.
|
// DNSLabels defines additional DNS labels configured in the peer.
|
||||||
DNSLabels []string
|
DNSLabels []string
|
||||||
// Performance configures the tunnel's buffer pool cap and batch size.
|
|
||||||
Performance Performance
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performance configures the embedded client's tunnel memory/throughput knobs.
|
|
||||||
//
|
|
||||||
// These settings are process-global: any non-nil field also becomes the
|
|
||||||
// default for Clients constructed by later embed.New calls in the same
|
|
||||||
// process. Nil fields are ignored.
|
|
||||||
type Performance struct {
|
|
||||||
// PreallocatedBuffersPerPool caps the per-tunnel buffer pool. Zero
|
|
||||||
// leaves the pool unbounded. Lower values trade throughput for a
|
|
||||||
// tighter memory ceiling. May also be changed on a running Client via
|
|
||||||
// Client.SetPerformance, provided this field was nonzero at construction.
|
|
||||||
PreallocatedBuffersPerPool *uint32
|
|
||||||
// MaxBatchSize overrides the number of packets the tunnel reads or
|
|
||||||
// writes per syscall, which also bounds eager buffer allocation per
|
|
||||||
// worker. Zero uses the platform default. Applied at construction
|
|
||||||
// only; ignored by Client.SetPerformance.
|
|
||||||
MaxBatchSize *uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateCredentials checks that exactly one credential type is provided
|
// validateCredentials checks that exactly one credential type is provided
|
||||||
@@ -202,7 +175,6 @@ func New(opts Options) (*Client, error) {
|
|||||||
DisableClientRoutes: &opts.DisableClientRoutes,
|
DisableClientRoutes: &opts.DisableClientRoutes,
|
||||||
DisableIPv6: &opts.DisableIPv6,
|
DisableIPv6: &opts.DisableIPv6,
|
||||||
BlockInbound: &opts.BlockInbound,
|
BlockInbound: &opts.BlockInbound,
|
||||||
BlockLANAccess: &opts.BlockLANAccess,
|
|
||||||
WireguardPort: opts.WireguardPort,
|
WireguardPort: opts.WireguardPort,
|
||||||
MTU: opts.MTU,
|
MTU: opts.MTU,
|
||||||
DNSLabels: parsedLabels,
|
DNSLabels: parsedLabels,
|
||||||
@@ -220,13 +192,6 @@ func New(opts Options) (*Client, error) {
|
|||||||
config.PrivateKey = opts.PrivateKey
|
config.PrivateKey = opts.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Performance.PreallocatedBuffersPerPool != nil {
|
|
||||||
wgdevice.SetPreallocatedBuffersPerPool(*opts.Performance.PreallocatedBuffersPerPool)
|
|
||||||
}
|
|
||||||
if opts.Performance.MaxBatchSize != nil {
|
|
||||||
wgdevice.SetMaxBatchSizeOverride(*opts.Performance.MaxBatchSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
deviceName: opts.DeviceName,
|
deviceName: opts.DeviceName,
|
||||||
setupKey: opts.SetupKey,
|
setupKey: opts.SetupKey,
|
||||||
@@ -440,21 +405,6 @@ func (c *Client) Expose(ctx context.Context, req ExposeRequest) (*ExposeSession,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IdentityForIP looks up a remote peer by its tunnel IP using the
|
|
||||||
// embedded client's status recorder. Returns the peer's WireGuard public
|
|
||||||
// key and FQDN. ok=false means the IP isn't in this client's peer
|
|
||||||
// roster — callers should treat that as "unknown peer".
|
|
||||||
func (c *Client) IdentityForIP(ip netip.Addr) (pubKey, fqdn string, ok bool) {
|
|
||||||
if !ip.IsValid() || c.recorder == nil {
|
|
||||||
return "", "", false
|
|
||||||
}
|
|
||||||
state, found := c.recorder.PeerStateByIP(ip.String())
|
|
||||||
if !found {
|
|
||||||
return "", "", false
|
|
||||||
}
|
|
||||||
return state.PubKey, state.FQDN, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status returns the current status of the client.
|
// Status returns the current status of the client.
|
||||||
func (c *Client) Status() (peer.FullStatus, error) {
|
func (c *Client) Status() (peer.FullStatus, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@@ -523,25 +473,6 @@ func (c *Client) VerifySSHHostKey(peerAddress string, key []byte) error {
|
|||||||
return sshcommon.VerifyHostKey(storedKey, key, peerAddress)
|
return sshcommon.VerifyHostKey(storedKey, key, peerAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPerformance retunes a running Client. Only PreallocatedBuffersPerPool
|
|
||||||
// takes effect, and only when it was nonzero at construction;
|
|
||||||
// MaxBatchSize is construction-only and returns an error if set here.
|
|
||||||
//
|
|
||||||
// Returns ErrClientNotStarted / ErrEngineNotStarted if the Client is not
|
|
||||||
// running yet.
|
|
||||||
func (c *Client) SetPerformance(t Performance) error {
|
|
||||||
if t.MaxBatchSize != nil {
|
|
||||||
return errors.New("MaxBatchSize is construction-only and cannot be changed at runtime")
|
|
||||||
}
|
|
||||||
engine, err := c.getEngine()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return engine.SetPerformance(internal.Performance{
|
|
||||||
PreallocatedBuffersPerPool: t.PreallocatedBuffersPerPool,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartCapture begins capturing packets on this client's tunnel device.
|
// StartCapture begins capturing packets on this client's tunnel device.
|
||||||
// Only one capture can be active at a time; starting a new one stops the previous.
|
// Only one capture can be active at a time; starting a new one stops the previous.
|
||||||
// Call StopCapture (or CaptureSession.Stop) to end it.
|
// Call StopCapture (or CaptureSession.Stop) to end it.
|
||||||
|
|||||||
@@ -260,15 +260,23 @@ WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "Publisher" "${COMP_NAME}"
|
|||||||
|
|
||||||
WriteRegStr ${REG_ROOT} "${UI_REG_APP_PATH}" "" "$INSTDIR\${UI_APP_EXE}"
|
WriteRegStr ${REG_ROOT} "${UI_REG_APP_PATH}" "" "$INSTDIR\${UI_APP_EXE}"
|
||||||
|
|
||||||
; Create autostart registry entry based on checkbox
|
; Drop Run, App Paths and Uninstall entries left in the 32-bit registry view
|
||||||
|
; or HKCU by legacy installers.
|
||||||
|
DetailPrint "Cleaning legacy 32-bit / HKCU entries..."
|
||||||
|
DeleteRegValue HKCU "${AUTOSTART_REG_KEY}" "${APP_NAME}"
|
||||||
|
SetRegView 32
|
||||||
|
DeleteRegValue HKLM "${AUTOSTART_REG_KEY}" "${APP_NAME}"
|
||||||
|
DeleteRegKey HKLM "${REG_APP_PATH}"
|
||||||
|
DeleteRegKey HKLM "${UI_REG_APP_PATH}"
|
||||||
|
DeleteRegKey HKLM "${UNINSTALL_PATH}"
|
||||||
|
SetRegView 64
|
||||||
|
|
||||||
DetailPrint "Autostart enabled: $AutostartEnabled"
|
DetailPrint "Autostart enabled: $AutostartEnabled"
|
||||||
${If} $AutostartEnabled == "1"
|
${If} $AutostartEnabled == "1"
|
||||||
WriteRegStr HKLM "${AUTOSTART_REG_KEY}" "${APP_NAME}" '"$INSTDIR\${UI_APP_EXE}.exe"'
|
WriteRegStr HKLM "${AUTOSTART_REG_KEY}" "${APP_NAME}" '"$INSTDIR\${UI_APP_EXE}.exe"'
|
||||||
DetailPrint "Added autostart registry entry: $INSTDIR\${UI_APP_EXE}.exe"
|
DetailPrint "Added autostart registry entry: $INSTDIR\${UI_APP_EXE}.exe"
|
||||||
${Else}
|
${Else}
|
||||||
DeleteRegValue HKLM "${AUTOSTART_REG_KEY}" "${APP_NAME}"
|
DeleteRegValue HKLM "${AUTOSTART_REG_KEY}" "${APP_NAME}"
|
||||||
; Legacy: pre-HKLM installs wrote to HKCU; clean that up too.
|
|
||||||
DeleteRegValue HKCU "${AUTOSTART_REG_KEY}" "${APP_NAME}"
|
|
||||||
DetailPrint "Autostart not enabled by user"
|
DetailPrint "Autostart not enabled by user"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
@@ -299,11 +307,16 @@ ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service uninstall'
|
|||||||
DetailPrint "Terminating Netbird UI process..."
|
DetailPrint "Terminating Netbird UI process..."
|
||||||
ExecWait `taskkill /im ${UI_APP_EXE}.exe /f`
|
ExecWait `taskkill /im ${UI_APP_EXE}.exe /f`
|
||||||
|
|
||||||
; Remove autostart registry entry
|
; Remove autostart entries from every view a previous installer may have used.
|
||||||
DetailPrint "Removing autostart registry entry if exists..."
|
DetailPrint "Removing autostart registry entry if exists..."
|
||||||
DeleteRegValue HKLM "${AUTOSTART_REG_KEY}" "${APP_NAME}"
|
DeleteRegValue HKLM "${AUTOSTART_REG_KEY}" "${APP_NAME}"
|
||||||
; Legacy: pre-HKLM installs wrote to HKCU; clean that up too.
|
|
||||||
DeleteRegValue HKCU "${AUTOSTART_REG_KEY}" "${APP_NAME}"
|
DeleteRegValue HKCU "${AUTOSTART_REG_KEY}" "${APP_NAME}"
|
||||||
|
SetRegView 32
|
||||||
|
DeleteRegValue HKLM "${AUTOSTART_REG_KEY}" "${APP_NAME}"
|
||||||
|
DeleteRegKey HKLM "${REG_APP_PATH}"
|
||||||
|
DeleteRegKey HKLM "${UI_REG_APP_PATH}"
|
||||||
|
DeleteRegKey HKLM "${UNINSTALL_PATH}"
|
||||||
|
SetRegView 64
|
||||||
|
|
||||||
; Handle data deletion based on checkbox
|
; Handle data deletion based on checkbox
|
||||||
DetailPrint "Checking if user requested data deletion..."
|
DetailPrint "Checking if user requested data deletion..."
|
||||||
|
|||||||
@@ -360,13 +360,7 @@ func isRedirectURLPortUsed(redirectURL string, excludedRanges []excludedPortRang
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// FreeBSD 15 disables connecting to INADDR_ANY (0.0.0.0) as a localhost
|
addr := fmt.Sprintf(":%s", port)
|
||||||
// alias by default, ensure explicit ip for localhost.
|
|
||||||
host := parsedURL.Hostname()
|
|
||||||
if host == "" {
|
|
||||||
host = "127.0.0.1"
|
|
||||||
}
|
|
||||||
addr := net.JoinHostPort(host, port)
|
|
||||||
conn, err := net.DialTimeout("tcp", addr, 3*time.Second)
|
conn, err := net.DialTimeout("tcp", addr, 3*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -339,7 +339,8 @@ func (c *HandlerChain) isHandlerMatch(qname string, entry HandlerEntry) bool {
|
|||||||
case entry.Pattern == ".":
|
case entry.Pattern == ".":
|
||||||
return true
|
return true
|
||||||
case entry.IsWildcard:
|
case entry.IsWildcard:
|
||||||
return strings.HasSuffix(qname, "."+entry.Pattern)
|
parts := strings.Split(strings.TrimSuffix(qname, entry.Pattern), ".")
|
||||||
|
return len(parts) >= 2 && strings.HasSuffix(qname, entry.Pattern)
|
||||||
default:
|
default:
|
||||||
// For non-wildcard patterns:
|
// For non-wildcard patterns:
|
||||||
// If handler wants subdomain matching, allow suffix match
|
// If handler wants subdomain matching, allow suffix match
|
||||||
|
|||||||
@@ -164,54 +164,6 @@ func TestHandlerChain_ServeDNS_DomainMatching(t *testing.T) {
|
|||||||
matchSubdomains: true,
|
matchSubdomains: true,
|
||||||
shouldMatch: true,
|
shouldMatch: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "wildcard label-boundary mismatch (suffix overlap)",
|
|
||||||
handlerDomain: "*.b.test.",
|
|
||||||
queryDomain: "x.ab.test.",
|
|
||||||
isWildcard: true,
|
|
||||||
matchSubdomains: false,
|
|
||||||
shouldMatch: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wildcard label-boundary match",
|
|
||||||
handlerDomain: "*.b.test.",
|
|
||||||
queryDomain: "x.b.test.",
|
|
||||||
isWildcard: true,
|
|
||||||
matchSubdomains: false,
|
|
||||||
shouldMatch: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wildcard multi-label match",
|
|
||||||
handlerDomain: "*.b.test.",
|
|
||||||
queryDomain: "x.y.b.test.",
|
|
||||||
isWildcard: true,
|
|
||||||
matchSubdomains: false,
|
|
||||||
shouldMatch: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wildcard no match on multi-label apex",
|
|
||||||
handlerDomain: "*.b.test.",
|
|
||||||
queryDomain: "b.test.",
|
|
||||||
isWildcard: true,
|
|
||||||
matchSubdomains: false,
|
|
||||||
shouldMatch: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wildcard no match on unrelated suffix containment",
|
|
||||||
handlerDomain: "*.example.com.",
|
|
||||||
queryDomain: "notexample.com.",
|
|
||||||
isWildcard: true,
|
|
||||||
matchSubdomains: false,
|
|
||||||
shouldMatch: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wildcard accepts pattern registered without trailing dot",
|
|
||||||
handlerDomain: "*.b.test",
|
|
||||||
queryDomain: "x.b.test.",
|
|
||||||
isWildcard: true,
|
|
||||||
matchSubdomains: false,
|
|
||||||
shouldMatch: true,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -321,19 +273,6 @@ func TestHandlerChain_ServeDNS_OverlappingDomains(t *testing.T) {
|
|||||||
expectedCalls: 1,
|
expectedCalls: 1,
|
||||||
expectedHandler: 2, // highest priority matching handler should be called
|
expectedHandler: 2, // highest priority matching handler should be called
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "overlapping wildcard suffixes route to correct handler",
|
|
||||||
handlers: []struct {
|
|
||||||
pattern string
|
|
||||||
priority int
|
|
||||||
}{
|
|
||||||
{pattern: "*.b.test.", priority: nbdns.PriorityDNSRoute},
|
|
||||||
{pattern: "*.ab.test.", priority: nbdns.PriorityDNSRoute},
|
|
||||||
},
|
|
||||||
queryDomain: "app.ab.test.",
|
|
||||||
expectedCalls: 1,
|
|
||||||
expectedHandler: 1,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "root zone with specific domain",
|
name: "root zone with specific domain",
|
||||||
handlers: []struct {
|
handlers: []struct {
|
||||||
|
|||||||
@@ -26,19 +26,6 @@ type resolver interface {
|
|||||||
LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error)
|
LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerConnectivity reports whether a tunnel IP belongs to a peer the
|
|
||||||
// client knows about and whether that peer is currently connected. The
|
|
||||||
// local resolver uses this to suppress A/AAAA answers whose RDATA points
|
|
||||||
// at a disconnected peer (typical case: a synthesized private-service
|
|
||||||
// record pointing at an embedded proxy peer that just went offline).
|
|
||||||
//
|
|
||||||
// known=false means the IP isn't in the local peerstore at all — the
|
|
||||||
// record is left alone (it points at something outside our mesh, e.g.
|
|
||||||
// a non-peer upstream).
|
|
||||||
type PeerConnectivity interface {
|
|
||||||
IsConnectedByIP(ip string) (known, connected bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Resolver struct {
|
type Resolver struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
records map[dns.Question][]dns.RR
|
records map[dns.Question][]dns.RR
|
||||||
@@ -46,11 +33,6 @@ type Resolver struct {
|
|||||||
// zones maps zone domain -> NonAuthoritative (true = non-authoritative, user-created zone)
|
// zones maps zone domain -> NonAuthoritative (true = non-authoritative, user-created zone)
|
||||||
zones map[domain.Domain]bool
|
zones map[domain.Domain]bool
|
||||||
resolver resolver
|
resolver resolver
|
||||||
// peerConn, when non-nil, is consulted on every A/AAAA answer to
|
|
||||||
// drop records pointing at disconnected peers. nil disables the
|
|
||||||
// filter and preserves the legacy "return whatever is registered"
|
|
||||||
// behaviour for callers that never wire a status source.
|
|
||||||
peerConn PeerConnectivity
|
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
@@ -67,15 +49,6 @@ func NewResolver() *Resolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPeerConnectivity wires the per-IP connectivity check used to filter
|
|
||||||
// out A/AAAA answers pointing at disconnected peers. Pass nil to disable.
|
|
||||||
// Safe to call multiple times; the latest value wins.
|
|
||||||
func (d *Resolver) SetPeerConnectivity(p PeerConnectivity) {
|
|
||||||
d.mu.Lock()
|
|
||||||
defer d.mu.Unlock()
|
|
||||||
d.peerConn = p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Resolver) MatchSubdomains() bool {
|
func (d *Resolver) MatchSubdomains() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -122,7 +95,6 @@ func (d *Resolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
replyMessage.RecursionAvailable = true
|
replyMessage.RecursionAvailable = true
|
||||||
|
|
||||||
result := d.lookupRecords(logger, question)
|
result := d.lookupRecords(logger, question)
|
||||||
result.records = d.filterDisconnectedPeerAnswers(logger, question, result.records)
|
|
||||||
replyMessage.Authoritative = !result.hasExternalData
|
replyMessage.Authoritative = !result.hasExternalData
|
||||||
replyMessage.Answer = result.records
|
replyMessage.Answer = result.records
|
||||||
replyMessage.Rcode = d.determineRcode(question, result)
|
replyMessage.Rcode = d.determineRcode(question, result)
|
||||||
@@ -464,78 +436,6 @@ func (d *Resolver) logDNSError(logger *log.Entry, hostname string, qtype uint16,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterDisconnectedPeerAnswers drops A/AAAA records whose RDATA matches
|
|
||||||
// a known but disconnected peer. The synthesized private-service zones
|
|
||||||
// emit one A record per connected proxy peer in a cluster; when a peer
|
|
||||||
// goes offline, the server-side refresh removes the record from the
|
|
||||||
// next netmap, but the client may still hold the previous netmap for a
|
|
||||||
// short window. This filter is the local belt to that braces — even on
|
|
||||||
// the stale netmap, the resolver hides the offline target.
|
|
||||||
//
|
|
||||||
// Records pointing at unknown IPs (outside the local peerstore, e.g.
|
|
||||||
// non-mesh upstreams) are never dropped. Non-A/AAAA records pass
|
|
||||||
// through untouched.
|
|
||||||
//
|
|
||||||
// Escape hatch: if filtering would leave the answer empty AND at least
|
|
||||||
// one record was filtered, the original list is returned. Better to
|
|
||||||
// hand the client a record that may not respond than NXDOMAIN it
|
|
||||||
// completely when every proxy peer is offline (the upstream may still
|
|
||||||
// be reachable some other way, or the peerstore may be stale).
|
|
||||||
func (d *Resolver) filterDisconnectedPeerAnswers(logger *log.Entry, question dns.Question, records []dns.RR) []dns.RR {
|
|
||||||
if len(records) == 0 {
|
|
||||||
return records
|
|
||||||
}
|
|
||||||
d.mu.RLock()
|
|
||||||
checker := d.peerConn
|
|
||||||
d.mu.RUnlock()
|
|
||||||
if checker == nil {
|
|
||||||
return records
|
|
||||||
}
|
|
||||||
|
|
||||||
kept := make([]dns.RR, 0, len(records))
|
|
||||||
var dropped int
|
|
||||||
for _, rr := range records {
|
|
||||||
ip := extractRecordIP(rr)
|
|
||||||
if ip == "" {
|
|
||||||
kept = append(kept, rr)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
known, connected := checker.IsConnectedByIP(ip)
|
|
||||||
if known && !connected {
|
|
||||||
dropped++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
kept = append(kept, rr)
|
|
||||||
}
|
|
||||||
if dropped == 0 {
|
|
||||||
return records
|
|
||||||
}
|
|
||||||
if len(kept) == 0 {
|
|
||||||
logger.Debugf("all %d answers for %s point at disconnected peers; returning the original list", dropped, question.Name)
|
|
||||||
return records
|
|
||||||
}
|
|
||||||
logger.Tracef("dropped %d disconnected-peer answer(s) for %s, returning %d", dropped, question.Name, len(kept))
|
|
||||||
return kept
|
|
||||||
}
|
|
||||||
|
|
||||||
// extractRecordIP returns the dotted-decimal / colon-hex IP carried by
|
|
||||||
// an A or AAAA record, or "" for any other record type.
|
|
||||||
func extractRecordIP(rr dns.RR) string {
|
|
||||||
switch r := rr.(type) {
|
|
||||||
case *dns.A:
|
|
||||||
if r.A == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return r.A.String()
|
|
||||||
case *dns.AAAA:
|
|
||||||
if r.AAAA == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return r.AAAA.String()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update replaces all zones and their records
|
// Update replaces all zones and their records
|
||||||
func (d *Resolver) Update(customZones []nbdns.CustomZone) {
|
func (d *Resolver) Update(customZones []nbdns.CustomZone) {
|
||||||
d.mu.Lock()
|
d.mu.Lock()
|
||||||
|
|||||||
@@ -30,21 +30,6 @@ func (m *mockResolver) LookupNetIP(ctx context.Context, network, host string) ([
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mockPeerConnectivity returns canned (known, connected) results per IP.
|
|
||||||
// Used by the disconnected-peer filter tests below. IPs not in the map
|
|
||||||
// are reported as unknown so the filter leaves them alone.
|
|
||||||
type mockPeerConnectivity struct {
|
|
||||||
byIP map[string]struct{ known, connected bool }
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPeerConnectivity) IsConnectedByIP(ip string) (known, connected bool) {
|
|
||||||
v, ok := m.byIP[ip]
|
|
||||||
if !ok {
|
|
||||||
return false, false
|
|
||||||
}
|
|
||||||
return v.known, v.connected
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLocalResolver_ServeDNS(t *testing.T) {
|
func TestLocalResolver_ServeDNS(t *testing.T) {
|
||||||
recordA := nbdns.SimpleRecord{
|
recordA := nbdns.SimpleRecord{
|
||||||
Name: "peera.netbird.cloud.",
|
Name: "peera.netbird.cloud.",
|
||||||
@@ -2667,114 +2652,3 @@ func BenchmarkIsInManagedZone_ManyZones(b *testing.B) {
|
|||||||
resolver.isInManagedZone(qname)
|
resolver.isInManagedZone(qname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLocalResolver_FilterDisconnectedPeerAnswers verifies the
|
|
||||||
// connectivity-aware filtering layered on top of lookupRecords:
|
|
||||||
// when an A record's IP belongs to a known peer that's disconnected,
|
|
||||||
// the record is dropped from the answer. Records for unknown IPs pass
|
|
||||||
// through. If filtering would empty the answer entirely and at least
|
|
||||||
// one record was dropped, the original list is restored (escape hatch
|
|
||||||
// for the "all proxies offline" case).
|
|
||||||
func TestLocalResolver_FilterDisconnectedPeerAnswers(t *testing.T) {
|
|
||||||
zone := "svc.cluster.netbird."
|
|
||||||
connectedRec := nbdns.SimpleRecord{
|
|
||||||
Name: zone,
|
|
||||||
Type: int(dns.TypeA),
|
|
||||||
Class: nbdns.DefaultClass,
|
|
||||||
TTL: 5,
|
|
||||||
RData: "100.64.0.10",
|
|
||||||
}
|
|
||||||
disconnectedRec := nbdns.SimpleRecord{
|
|
||||||
Name: zone,
|
|
||||||
Type: int(dns.TypeA),
|
|
||||||
Class: nbdns.DefaultClass,
|
|
||||||
TTL: 5,
|
|
||||||
RData: "100.64.0.11",
|
|
||||||
}
|
|
||||||
unknownRec := nbdns.SimpleRecord{
|
|
||||||
Name: zone,
|
|
||||||
Type: int(dns.TypeA),
|
|
||||||
Class: nbdns.DefaultClass,
|
|
||||||
TTL: 5,
|
|
||||||
RData: "203.0.113.5",
|
|
||||||
}
|
|
||||||
|
|
||||||
type ipState struct{ known, connected bool }
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
records []nbdns.SimpleRecord
|
|
||||||
connByIP map[string]ipState
|
|
||||||
wantInOrder []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "drops disconnected peer, keeps connected",
|
|
||||||
records: []nbdns.SimpleRecord{connectedRec, disconnectedRec},
|
|
||||||
connByIP: map[string]ipState{
|
|
||||||
"100.64.0.10": {known: true, connected: true},
|
|
||||||
"100.64.0.11": {known: true, connected: false},
|
|
||||||
},
|
|
||||||
wantInOrder: []string{"100.64.0.10"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "unknown IPs pass through untouched",
|
|
||||||
records: []nbdns.SimpleRecord{unknownRec, disconnectedRec},
|
|
||||||
connByIP: map[string]ipState{
|
|
||||||
"100.64.0.11": {known: true, connected: false},
|
|
||||||
},
|
|
||||||
wantInOrder: []string{"203.0.113.5"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "all disconnected falls back to original list",
|
|
||||||
records: []nbdns.SimpleRecord{disconnectedRec, connectedRec},
|
|
||||||
connByIP: map[string]ipState{
|
|
||||||
"100.64.0.10": {known: true, connected: false},
|
|
||||||
"100.64.0.11": {known: true, connected: false},
|
|
||||||
},
|
|
||||||
wantInOrder: []string{"100.64.0.11", "100.64.0.10"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no checker wired returns all records",
|
|
||||||
records: []nbdns.SimpleRecord{connectedRec, disconnectedRec},
|
|
||||||
connByIP: nil,
|
|
||||||
wantInOrder: []string{"100.64.0.10", "100.64.0.11"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
resolver := NewResolver()
|
|
||||||
if tc.connByIP != nil {
|
|
||||||
cm := mockPeerConnectivity{byIP: make(map[string]struct{ known, connected bool }, len(tc.connByIP))}
|
|
||||||
for ip, st := range tc.connByIP {
|
|
||||||
cm.byIP[ip] = struct{ known, connected bool }{st.known, st.connected}
|
|
||||||
}
|
|
||||||
resolver.SetPeerConnectivity(cm)
|
|
||||||
}
|
|
||||||
resolver.Update([]nbdns.CustomZone{{
|
|
||||||
Domain: strings.TrimSuffix(zone, "."),
|
|
||||||
Records: tc.records,
|
|
||||||
NonAuthoritative: true,
|
|
||||||
}})
|
|
||||||
|
|
||||||
var got *dns.Msg
|
|
||||||
writer := &test.MockResponseWriter{
|
|
||||||
WriteMsgFunc: func(m *dns.Msg) error {
|
|
||||||
got = m
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
req := new(dns.Msg).SetQuestion(zone, dns.TypeA)
|
|
||||||
resolver.ServeDNS(writer, req)
|
|
||||||
|
|
||||||
require.NotNil(t, got, "resolver must produce a response")
|
|
||||||
require.Len(t, got.Answer, len(tc.wantInOrder),
|
|
||||||
"answer count must match expected: %v", tc.wantInOrder)
|
|
||||||
for i, want := range tc.wantInOrder {
|
|
||||||
a, ok := got.Answer[i].(*dns.A)
|
|
||||||
require.True(t, ok, "answer[%d] must be an A record", i)
|
|
||||||
assert.Equal(t, want, a.A.String(),
|
|
||||||
"answer[%d] expected %s got %s", i, want, a.A.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -301,11 +301,6 @@ func newDefaultServer(
|
|||||||
warningDelayBase: defaultWarningDelayBase,
|
warningDelayBase: defaultWarningDelayBase,
|
||||||
healthRefresh: make(chan struct{}, 1),
|
healthRefresh: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
// Wire the local resolver against the peer status recorder so it can
|
|
||||||
// suppress A/AAAA answers that point at disconnected peers (typical
|
|
||||||
// case: synthesised private-service records pointing at an embedded
|
|
||||||
// proxy peer that just went offline).
|
|
||||||
defaultServer.localResolver.SetPeerConnectivity(localPeerConnectivity{statusRecorder})
|
|
||||||
|
|
||||||
// register with root zone, handler chain takes care of the routing
|
// register with root zone, handler chain takes care of the routing
|
||||||
dnsService.RegisterMux(".", handlerChain)
|
dnsService.RegisterMux(".", handlerChain)
|
||||||
@@ -1391,25 +1386,3 @@ func (s *DefaultServer) PopulateManagementDomain(mgmtURL *url.URL) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// localPeerConnectivity adapts *peer.Status to local.PeerConnectivity so
|
|
||||||
// the local resolver can ask "is this IP a known peer and is it
|
|
||||||
// connected?" without taking on the peer package as a dependency.
|
|
||||||
// A nil status recorder always reports known=false so the resolver
|
|
||||||
// short-circuits to the legacy "return everything" path.
|
|
||||||
type localPeerConnectivity struct {
|
|
||||||
status *peer.Status
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsConnectedByIP looks the IP up in the peerstore and surfaces both
|
|
||||||
// the known and connected bits. Used by Resolver.filterDisconnectedPeerAnswers.
|
|
||||||
func (l localPeerConnectivity) IsConnectedByIP(ip string) (known, connected bool) {
|
|
||||||
if l.status == nil {
|
|
||||||
return false, false
|
|
||||||
}
|
|
||||||
state, ok := l.status.PeerStateByIP(ip)
|
|
||||||
if !ok {
|
|
||||||
return false, false
|
|
||||||
}
|
|
||||||
return true, state.ConnStatus == peer.StatusConnected
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1967,29 +1967,6 @@ func (e *Engine) GetClientMetrics() *metrics.ClientMetrics {
|
|||||||
return e.clientMetrics
|
return e.clientMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performance bundles runtime-adjustable tunnel pool knobs.
|
|
||||||
// See Engine.SetPerformance. Nil fields are ignored.
|
|
||||||
type Performance struct {
|
|
||||||
PreallocatedBuffersPerPool *uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPerformance applies the given tuning to this engine's live Device.
|
|
||||||
func (e *Engine) SetPerformance(t Performance) error {
|
|
||||||
e.syncMsgMux.Lock()
|
|
||||||
defer e.syncMsgMux.Unlock()
|
|
||||||
if e.wgInterface == nil {
|
|
||||||
return fmt.Errorf("wg interface not initialized")
|
|
||||||
}
|
|
||||||
dev := e.wgInterface.GetWGDevice()
|
|
||||||
if dev == nil {
|
|
||||||
return fmt.Errorf("wg device not initialized")
|
|
||||||
}
|
|
||||||
if t.PreallocatedBuffersPerPool != nil {
|
|
||||||
dev.SetPreallocatedBuffersPerPool(*t.PreallocatedBuffersPerPool)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
||||||
iface, err := net.InterfaceByName(ifaceName)
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||||
"github.com/netbirdio/netbird/management/server/job"
|
"github.com/netbirdio/netbird/management/server/job"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator/validator"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
||||||
@@ -66,8 +66,8 @@ import (
|
|||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
mgmt "github.com/netbirdio/netbird/shared/management/client"
|
mgmt "github.com/netbirdio/netbird/shared/management/client"
|
||||||
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
"github.com/netbirdio/netbird/shared/netiputil"
|
|
||||||
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
||||||
|
"github.com/netbirdio/netbird/shared/netiputil"
|
||||||
signal "github.com/netbirdio/netbird/shared/signal/client"
|
signal "github.com/netbirdio/netbird/shared/signal/client"
|
||||||
"github.com/netbirdio/netbird/shared/signal/proto"
|
"github.com/netbirdio/netbird/shared/signal/proto"
|
||||||
signalServer "github.com/netbirdio/netbird/signal/server"
|
signalServer "github.com/netbirdio/netbird/signal/server"
|
||||||
@@ -1641,7 +1641,7 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
ia, _ := validator.NewIntegratedValidator(context.Background(), peersManager, nil, eventStore, cacheStore)
|
ia, _ := integrations.NewIntegratedValidator(context.Background(), peersManager, nil, eventStore, cacheStore)
|
||||||
|
|
||||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func routeCheck(ctx context.Context, fd int, nexthopv4, nexthopv6 systemops.Next
|
|||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
// handle route changes
|
// handle route changes
|
||||||
case unix.RTM_ADD, syscall.RTM_DELETE:
|
case unix.RTM_ADD, syscall.RTM_DELETE:
|
||||||
route, flags, err := parseRouteMessage(buf[:n])
|
route, err := parseRouteMessage(buf[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Network monitor: error parsing routing message: %v", err)
|
log.Debugf("Network monitor: error parsing routing message: %v", err)
|
||||||
continue
|
continue
|
||||||
@@ -66,10 +66,6 @@ func routeCheck(ctx context.Context, fd int, nexthopv4, nexthopv6 systemops.Next
|
|||||||
}
|
}
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case unix.RTM_ADD:
|
case unix.RTM_ADD:
|
||||||
if systemops.IgnoreAddedDefaultRoute(flags) {
|
|
||||||
log.Debugf("Network monitor: ignoring added default route via %s, interface %s, flags %#x", route.Gw, intf, flags)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Infof("Network monitor: default route changed: via %s, interface %s", route.Gw, intf)
|
log.Infof("Network monitor: default route changed: via %s, interface %s", route.Gw, intf)
|
||||||
return nil
|
return nil
|
||||||
case unix.RTM_DELETE:
|
case unix.RTM_DELETE:
|
||||||
@@ -82,26 +78,22 @@ func routeCheck(ctx context.Context, fd int, nexthopv4, nexthopv6 systemops.Next
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRouteMessage(buf []byte) (*systemops.Route, int, error) {
|
func parseRouteMessage(buf []byte) (*systemops.Route, error) {
|
||||||
msgs, err := route.ParseRIB(route.RIBTypeRoute, buf)
|
msgs, err := route.ParseRIB(route.RIBTypeRoute, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, fmt.Errorf("parse RIB: %v", err)
|
return nil, fmt.Errorf("parse RIB: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(msgs) != 1 {
|
if len(msgs) != 1 {
|
||||||
return nil, 0, fmt.Errorf("unexpected RIB message msgs: %v", msgs)
|
return nil, fmt.Errorf("unexpected RIB message msgs: %v", msgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, ok := msgs[0].(*route.RouteMessage)
|
msg, ok := msgs[0].(*route.RouteMessage)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, 0, fmt.Errorf("unexpected RIB message type: %T", msgs[0])
|
return nil, fmt.Errorf("unexpected RIB message type: %T", msgs[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := systemops.MsgToRoute(msg)
|
return systemops.MsgToRoute(msg)
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
return r, msg.Flags, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitReadable blocks until fd has data to read, or ctx is cancelled.
|
// waitReadable blocks until fd has data to read, or ctx is cancelled.
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/peer/id"
|
"github.com/netbirdio/netbird/client/internal/peer/id"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer/worker"
|
"github.com/netbirdio/netbird/client/internal/peer/worker"
|
||||||
"github.com/netbirdio/netbird/client/internal/portforward"
|
"github.com/netbirdio/netbird/client/internal/portforward"
|
||||||
"github.com/netbirdio/netbird/client/internal/rosenpass"
|
|
||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
||||||
@@ -900,7 +899,7 @@ func (conn *Conn) presharedKey(remoteRosenpassKey []byte) *wgtypes.Key {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to deterministic key if no NetBird PSK is configured
|
// Fallback to deterministic key if no NetBird PSK is configured
|
||||||
determKey, err := rosenpass.DeterministicSeedKey(conn.config.LocalKey, conn.config.Key)
|
determKey, err := conn.rosenpassDetermKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Log.Errorf("failed to generate Rosenpass initial key: %v", err)
|
conn.Log.Errorf("failed to generate Rosenpass initial key: %v", err)
|
||||||
return nil
|
return nil
|
||||||
@@ -909,6 +908,26 @@ func (conn *Conn) presharedKey(remoteRosenpassKey []byte) *wgtypes.Key {
|
|||||||
return determKey
|
return determKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: move this logic into Rosenpass package
|
||||||
|
func (conn *Conn) rosenpassDetermKey() (*wgtypes.Key, error) {
|
||||||
|
lk := []byte(conn.config.LocalKey)
|
||||||
|
rk := []byte(conn.config.Key) // remote key
|
||||||
|
var keyInput []byte
|
||||||
|
if string(lk) > string(rk) {
|
||||||
|
//nolint:gocritic
|
||||||
|
keyInput = append(lk[:16], rk[:16]...)
|
||||||
|
} else {
|
||||||
|
//nolint:gocritic
|
||||||
|
keyInput = append(rk[:16], lk[:16]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := wgtypes.NewKey(keyInput)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &key, nil
|
||||||
|
}
|
||||||
|
|
||||||
func isController(config ConnConfig) bool {
|
func isController(config ConnConfig) bool {
|
||||||
return config.LocalKey > config.Key
|
return config.LocalKey > config.Key
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,12 +185,9 @@ func (s *StatusChangeSubscription) Events() chan map[string]RouterState {
|
|||||||
return s.eventsChan
|
return s.eventsChan
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status holds a state of peers, signal, management connections and relays.
|
// Status holds a state of peers, signal, management connections and relays
|
||||||
// mux is an RWMutex so hot read paths (notably PeerStateByIP, called for
|
|
||||||
// every private-service request) don't contend against each other.
|
|
||||||
// Pure read methods take RLock; anything that mutates state takes Lock.
|
|
||||||
type Status struct {
|
type Status struct {
|
||||||
mux sync.RWMutex
|
mux sync.Mutex
|
||||||
peers map[string]State
|
peers map[string]State
|
||||||
changeNotify map[string]map[string]*StatusChangeSubscription // map[peerID]map[subscriptionID]*StatusChangeSubscription
|
changeNotify map[string]map[string]*StatusChangeSubscription // map[peerID]map[subscriptionID]*StatusChangeSubscription
|
||||||
signalState bool
|
signalState bool
|
||||||
@@ -286,8 +283,8 @@ func (d *Status) AddPeer(peerPubKey string, fqdn string, ip string, ipv6 string)
|
|||||||
|
|
||||||
// GetPeer adds peer to Daemon status map
|
// GetPeer adds peer to Daemon status map
|
||||||
func (d *Status) GetPeer(peerPubKey string) (State, error) {
|
func (d *Status) GetPeer(peerPubKey string) (State, error) {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
state, ok := d.peers[peerPubKey]
|
state, ok := d.peers[peerPubKey]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -297,8 +294,8 @@ func (d *Status) GetPeer(peerPubKey string) (State, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) PeerByIP(ip string) (string, bool) {
|
func (d *Status) PeerByIP(ip string) (string, bool) {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
for _, state := range d.peers {
|
for _, state := range d.peers {
|
||||||
if state.IP == ip {
|
if state.IP == ip {
|
||||||
@@ -308,25 +305,6 @@ func (d *Status) PeerByIP(ip string) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerStateByIP returns the full peer State for the given tunnel IP.
|
|
||||||
// Matches against either the IPv4 (State.IP) or IPv6 (State.IPv6) tunnel
|
|
||||||
// address so dual-stack peers are reachable on either family. Returns the
|
|
||||||
// zero State and false when no peer matches or the input is empty.
|
|
||||||
func (d *Status) PeerStateByIP(ip string) (State, bool) {
|
|
||||||
if ip == "" {
|
|
||||||
return State{}, false
|
|
||||||
}
|
|
||||||
d.mux.RLock()
|
|
||||||
defer d.mux.RUnlock()
|
|
||||||
|
|
||||||
for _, state := range d.peers {
|
|
||||||
if (state.IP != "" && state.IP == ip) || (state.IPv6 != "" && state.IPv6 == ip) {
|
|
||||||
return state, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return State{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemovePeer removes peer from Daemon status map
|
// RemovePeer removes peer from Daemon status map
|
||||||
func (d *Status) RemovePeer(peerPubKey string) error {
|
func (d *Status) RemovePeer(peerPubKey string) error {
|
||||||
d.mux.Lock()
|
d.mux.Lock()
|
||||||
@@ -724,8 +702,8 @@ func (d *Status) UnsubscribePeerStateChanges(subscription *StatusChangeSubscript
|
|||||||
|
|
||||||
// GetLocalPeerState returns the local peer state
|
// GetLocalPeerState returns the local peer state
|
||||||
func (d *Status) GetLocalPeerState() LocalPeerState {
|
func (d *Status) GetLocalPeerState() LocalPeerState {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
return d.localPeer.Clone()
|
return d.localPeer.Clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -931,8 +909,8 @@ func (d *Status) DeleteResolvedDomainsStates(domain domain.Domain) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) GetRosenpassState() RosenpassState {
|
func (d *Status) GetRosenpassState() RosenpassState {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
return RosenpassState{
|
return RosenpassState{
|
||||||
d.rosenpassEnabled,
|
d.rosenpassEnabled,
|
||||||
d.rosenpassPermissive,
|
d.rosenpassPermissive,
|
||||||
@@ -940,14 +918,14 @@ func (d *Status) GetRosenpassState() RosenpassState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) GetLazyConnection() bool {
|
func (d *Status) GetLazyConnection() bool {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
return d.lazyConnectionEnabled
|
return d.lazyConnectionEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) GetManagementState() ManagementState {
|
func (d *Status) GetManagementState() ManagementState {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
return ManagementState{
|
return ManagementState{
|
||||||
d.mgmAddress,
|
d.mgmAddress,
|
||||||
d.managementState,
|
d.managementState,
|
||||||
@@ -973,8 +951,8 @@ func (d *Status) UpdateLatency(pubKey string, latency time.Duration) error {
|
|||||||
|
|
||||||
// IsLoginRequired determines if a peer's login has expired.
|
// IsLoginRequired determines if a peer's login has expired.
|
||||||
func (d *Status) IsLoginRequired() bool {
|
func (d *Status) IsLoginRequired() bool {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
// if peer is connected to the management then login is not expired
|
// if peer is connected to the management then login is not expired
|
||||||
if d.managementState {
|
if d.managementState {
|
||||||
@@ -989,8 +967,8 @@ func (d *Status) IsLoginRequired() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) GetSignalState() SignalState {
|
func (d *Status) GetSignalState() SignalState {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
return SignalState{
|
return SignalState{
|
||||||
d.signalAddress,
|
d.signalAddress,
|
||||||
d.signalState,
|
d.signalState,
|
||||||
@@ -1000,8 +978,8 @@ func (d *Status) GetSignalState() SignalState {
|
|||||||
|
|
||||||
// GetRelayStates returns the stun/turn/permanent relay states
|
// GetRelayStates returns the stun/turn/permanent relay states
|
||||||
func (d *Status) GetRelayStates() []relay.ProbeResult {
|
func (d *Status) GetRelayStates() []relay.ProbeResult {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
if d.relayMgr == nil {
|
if d.relayMgr == nil {
|
||||||
return d.relayStates
|
return d.relayStates
|
||||||
}
|
}
|
||||||
@@ -1030,8 +1008,8 @@ func (d *Status) GetRelayStates() []relay.ProbeResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) ForwardingRules() []firewall.ForwardRule {
|
func (d *Status) ForwardingRules() []firewall.ForwardRule {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
if d.ingressGwMgr == nil {
|
if d.ingressGwMgr == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1040,16 +1018,16 @@ func (d *Status) ForwardingRules() []firewall.ForwardRule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) GetDNSStates() []NSGroupState {
|
func (d *Status) GetDNSStates() []NSGroupState {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
// shallow copy is good enough, as slices fields are currently not updated
|
// shallow copy is good enough, as slices fields are currently not updated
|
||||||
return slices.Clone(d.nsGroupStates)
|
return slices.Clone(d.nsGroupStates)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) GetResolvedDomainsStates() map[domain.Domain]ResolvedDomainInfo {
|
func (d *Status) GetResolvedDomainsStates() map[domain.Domain]ResolvedDomainInfo {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
return maps.Clone(d.resolvedDomainsStates)
|
return maps.Clone(d.resolvedDomainsStates)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1065,8 +1043,8 @@ func (d *Status) GetFullStatus() FullStatus {
|
|||||||
LazyConnectionEnabled: d.GetLazyConnection(),
|
LazyConnectionEnabled: d.GetLazyConnection(),
|
||||||
}
|
}
|
||||||
|
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
fullStatus.LocalPeerState = d.localPeer
|
fullStatus.LocalPeerState = d.localPeer
|
||||||
|
|
||||||
@@ -1241,8 +1219,8 @@ func (d *Status) SetWgIface(wgInterface WGIfaceStatus) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) PeersStatus() (*configurer.Stats, error) {
|
func (d *Status) PeersStatus() (*configurer.Stats, error) {
|
||||||
d.mux.RLock()
|
d.mux.Lock()
|
||||||
defer d.mux.RUnlock()
|
defer d.mux.Unlock()
|
||||||
if d.wgIface == nil {
|
if d.wgIface == nil {
|
||||||
return nil, fmt.Errorf("wgInterface is nil, cannot retrieve peers status")
|
return nil, fmt.Errorf("wgInterface is nil, cannot retrieve peers status")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,33 +63,6 @@ func TestUpdatePeerState(t *testing.T) {
|
|||||||
assert.Equal(t, ip, state.IP, "ip should be equal")
|
assert.Equal(t, ip, state.IP, "ip should be equal")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStatus_PeerStateByIP(t *testing.T) {
|
|
||||||
status := NewRecorder("https://mgm")
|
|
||||||
req := require.New(t)
|
|
||||||
|
|
||||||
req.NoError(status.AddPeer("pk-1", "peer-1.netbird", "100.64.0.10", ""))
|
|
||||||
req.NoError(status.AddPeer("pk-2", "peer-2.netbird", "100.64.0.11", ""))
|
|
||||||
|
|
||||||
state, ok := status.PeerStateByIP("100.64.0.10")
|
|
||||||
req.True(ok, "known tunnel IP should resolve to a peer state")
|
|
||||||
req.Equal("pk-1", state.PubKey, "matching state must carry the right pub key")
|
|
||||||
req.Equal("peer-1.netbird", state.FQDN, "matching state must carry the right FQDN")
|
|
||||||
|
|
||||||
_, ok = status.PeerStateByIP("100.64.0.99")
|
|
||||||
req.False(ok, "unknown IP must report ok=false")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatus_PeerStateByIP_MatchesIPv6(t *testing.T) {
|
|
||||||
status := NewRecorder("https://mgm")
|
|
||||||
req := require.New(t)
|
|
||||||
|
|
||||||
req.NoError(status.AddPeer("pk-1", "peer-1.netbird", "100.64.0.10", "fd00::1"))
|
|
||||||
|
|
||||||
state, ok := status.PeerStateByIP("fd00::1")
|
|
||||||
req.True(ok, "IPv6-only match must resolve to the peer state")
|
|
||||||
req.Equal("pk-1", state.PubKey, "matching state must carry the right pub key")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatus_UpdatePeerFQDN(t *testing.T) {
|
func TestStatus_UpdatePeerFQDN(t *testing.T) {
|
||||||
key := "abc"
|
key := "abc"
|
||||||
fqdn := "peer-a.netbird.local"
|
fqdn := "peer-a.netbird.local"
|
||||||
|
|||||||
@@ -179,10 +179,8 @@ func getDefaultGateway() (gateway net.IP, localIP net.IP, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dst := net.IPv4zero
|
dst := net.IPv4zero
|
||||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
if runtime.GOOS == "linux" {
|
||||||
// go-netroute v0.4.0 rejects unspecified destinations client-side on Linux/Android.
|
// go-netroute v0.4.0 rejects unspecified destinations client-side on Linux.
|
||||||
// TODO: on android/ios, use platform APIs (ConnectivityManager.getLinkProperties /
|
|
||||||
// NWPathMonitor) when netlink-based lookup is restricted or unavailable.
|
|
||||||
dst = net.IPv4(0, 0, 0, 1)
|
dst = net.IPv4(0, 0, 0, 1)
|
||||||
}
|
}
|
||||||
_, gateway, localIP, err = router.Route(dst)
|
_, gateway, localIP, err = router.Route(dst)
|
||||||
@@ -205,7 +203,7 @@ func getDefaultGateway6() (gateway net.IP, localIP net.IP, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dst := net.IPv6zero
|
dst := net.IPv6zero
|
||||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
if runtime.GOOS == "linux" {
|
||||||
// ::2
|
// ::2
|
||||||
dst = net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
|
dst = net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,15 +28,6 @@ func hashRosenpassKey(key []byte) string {
|
|||||||
return hex.EncodeToString(hasher.Sum(nil))
|
return hex.EncodeToString(hasher.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
// rpServer is the subset of rp.Server used by Manager. Defined as an interface
|
|
||||||
// so tests can substitute a mock without spinning up a real UDP server.
|
|
||||||
type rpServer interface {
|
|
||||||
AddPeer(rp.PeerConfig) (rp.PeerID, error)
|
|
||||||
RemovePeer(rp.PeerID) error
|
|
||||||
Run() error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
ifaceName string
|
ifaceName string
|
||||||
spk []byte
|
spk []byte
|
||||||
@@ -45,7 +36,7 @@ type Manager struct {
|
|||||||
preSharedKey *[32]byte
|
preSharedKey *[32]byte
|
||||||
rpPeerIDs map[string]*rp.PeerID
|
rpPeerIDs map[string]*rp.PeerID
|
||||||
rpWgHandler *NetbirdHandler
|
rpWgHandler *NetbirdHandler
|
||||||
server rpServer
|
server *rp.Server
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
port int
|
port int
|
||||||
wgIface PresharedKeySetter
|
wgIface PresharedKeySetter
|
||||||
@@ -60,22 +51,7 @@ func NewManager(preSharedKey *wgtypes.Key, wgIfaceName string) (*Manager, error)
|
|||||||
|
|
||||||
rpKeyHash := hashRosenpassKey(public)
|
rpKeyHash := hashRosenpassKey(public)
|
||||||
log.Tracef("generated new rosenpass key pair with public key %s", rpKeyHash)
|
log.Tracef("generated new rosenpass key pair with public key %s", rpKeyHash)
|
||||||
return &Manager{
|
return &Manager{ifaceName: wgIfaceName, rpKeyHash: rpKeyHash, spk: public, ssk: secret, preSharedKey: (*[32]byte)(preSharedKey), rpPeerIDs: make(map[string]*rp.PeerID), lock: sync.Mutex{}}, nil
|
||||||
ifaceName: wgIfaceName,
|
|
||||||
rpKeyHash: rpKeyHash,
|
|
||||||
spk: public,
|
|
||||||
ssk: secret,
|
|
||||||
preSharedKey: (*[32]byte)(preSharedKey),
|
|
||||||
rpPeerIDs: make(map[string]*rp.PeerID),
|
|
||||||
// rpWgHandler is created here (instead of only in generateConfig) so it
|
|
||||||
// is never nil between NewManager and Run(). Otherwise an early
|
|
||||||
// OnConnected call (race observed on Android, issue #4341) panics on
|
|
||||||
// nil receiver in addPeer -> m.rpWgHandler.AddPeer. generateConfig will
|
|
||||||
// replace it with a fresh handler on each Run() to clear stale peer
|
|
||||||
// state from previous engine sessions.
|
|
||||||
rpWgHandler: NewNetbirdHandler(),
|
|
||||||
lock: sync.Mutex{},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetPubKey() []byte {
|
func (m *Manager) GetPubKey() []byte {
|
||||||
@@ -89,16 +65,6 @@ func (m *Manager) GetAddress() *net.UDPAddr {
|
|||||||
|
|
||||||
// addPeer adds a new peer to the Rosenpass server
|
// addPeer adds a new peer to the Rosenpass server
|
||||||
func (m *Manager) addPeer(rosenpassPubKey []byte, rosenpassAddr string, wireGuardIP string, wireGuardPubKey string) error {
|
func (m *Manager) addPeer(rosenpassPubKey []byte, rosenpassAddr string, wireGuardIP string, wireGuardPubKey string) error {
|
||||||
// Defense in depth against issue #4341 (Android crash): if Run() has not
|
|
||||||
// completed yet, m.server / m.rpWgHandler may be nil. Return an explicit
|
|
||||||
// error instead of panicking on nil-receiver dereference.
|
|
||||||
if m.server == nil {
|
|
||||||
return fmt.Errorf("rosenpass server not initialized")
|
|
||||||
}
|
|
||||||
if m.rpWgHandler == nil {
|
|
||||||
return fmt.Errorf("rosenpass wg handler not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
pcfg := rp.PeerConfig{PublicKey: rosenpassPubKey}
|
pcfg := rp.PeerConfig{PublicKey: rosenpassPubKey}
|
||||||
if m.preSharedKey != nil {
|
if m.preSharedKey != nil {
|
||||||
@@ -113,16 +79,6 @@ func (m *Manager) addPeer(rosenpassPubKey []byte, rosenpassAddr string, wireGuar
|
|||||||
if pcfg.Endpoint, err = net.ResolveUDPAddr("udp", peerAddr); err != nil {
|
if pcfg.Endpoint, err = net.ResolveUDPAddr("udp", peerAddr); err != nil {
|
||||||
return fmt.Errorf("failed to resolve peer endpoint address: %w", err)
|
return fmt.Errorf("failed to resolve peer endpoint address: %w", err)
|
||||||
}
|
}
|
||||||
// Our local Rosenpass UDP server binds on the IPv6 wildcard ([::]) — see
|
|
||||||
// GetAddress(). The remote peer's endpoint (pcfg.Endpoint) is the destination
|
|
||||||
// our server will sendto when initiating handshakes. ResolveUDPAddr returns a
|
|
||||||
// 4-byte IPv4 for IPv4 hosts, which the kernel rejects (EDESTADDRREQ) when
|
|
||||||
// sent from an AF_INET6 socket. Normalize the remote endpoint to IPv4-mapped
|
|
||||||
// IPv6 so its address family matches our listening socket.
|
|
||||||
// TODO: maybe bind the Rosenpass UDP server to the peer wg IP addr
|
|
||||||
if v4 := pcfg.Endpoint.IP.To4(); v4 != nil {
|
|
||||||
pcfg.Endpoint.IP = v4.To16()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
peerID, err := m.server.AddPeer(pcfg)
|
peerID, err := m.server.AddPeer(pcfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -226,31 +182,24 @@ func (m *Manager) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := rp.NewUDPServer(conf)
|
m.server, err = rp.NewUDPServer(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.lock.Lock()
|
|
||||||
m.server = server
|
|
||||||
m.lock.Unlock()
|
|
||||||
|
|
||||||
log.Infof("starting rosenpass server on port %d", m.port)
|
log.Infof("starting rosenpass server on port %d", m.port)
|
||||||
|
|
||||||
return server.Run()
|
return m.server.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the Rosenpass server
|
// Close closes the Rosenpass server
|
||||||
func (m *Manager) Close() error {
|
func (m *Manager) Close() error {
|
||||||
m.lock.Lock()
|
if m.server != nil {
|
||||||
server := m.server
|
err := m.server.Close()
|
||||||
m.server = nil
|
if err != nil {
|
||||||
m.lock.Unlock()
|
log.Errorf("failed closing local rosenpass server")
|
||||||
if server == nil {
|
}
|
||||||
return nil
|
m.server = nil
|
||||||
}
|
|
||||||
if err := server.Close(); err != nil {
|
|
||||||
log.Errorf("failed closing local rosenpass server: %v", err)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,412 +1,14 @@
|
|||||||
package rosenpass
|
package rosenpass
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
rp "cunicu.li/go-rosenpass"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- test doubles -----------------------------------------------------------
|
|
||||||
|
|
||||||
type addPeerCall struct {
|
|
||||||
cfg rp.PeerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type removePeerCall struct {
|
|
||||||
id rp.PeerID
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockServer struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
addCalls []addPeerCall
|
|
||||||
removed []removePeerCall
|
|
||||||
nextID rp.PeerID
|
|
||||||
addErr error
|
|
||||||
removeErr error
|
|
||||||
closed bool
|
|
||||||
ran bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockServer) AddPeer(cfg rp.PeerConfig) (rp.PeerID, error) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
m.addCalls = append(m.addCalls, addPeerCall{cfg: cfg})
|
|
||||||
if m.addErr != nil {
|
|
||||||
return rp.PeerID{}, m.addErr
|
|
||||||
}
|
|
||||||
// Increment a byte in nextID so distinct peers get distinct IDs.
|
|
||||||
m.nextID[0]++
|
|
||||||
return m.nextID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockServer) RemovePeer(id rp.PeerID) error {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
m.removed = append(m.removed, removePeerCall{id: id})
|
|
||||||
return m.removeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockServer) Run() error { m.ran = true; return nil }
|
|
||||||
func (m *mockServer) Close() error { m.closed = true; return nil }
|
|
||||||
|
|
||||||
type setPSKCall struct {
|
|
||||||
peerKey string
|
|
||||||
psk wgtypes.Key
|
|
||||||
updateOnly bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockIface struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
calls []setPSKCall
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockIface) SetPresharedKey(peerKey string, psk wgtypes.Key, updateOnly bool) error {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
m.calls = append(m.calls, setPSKCall{peerKey: peerKey, psk: psk, updateOnly: updateOnly})
|
|
||||||
return m.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// newTestManager builds a Manager with deterministic spk so tie-break
|
|
||||||
// against a peer pubkey is controllable from tests. The provided spk byte
|
|
||||||
// becomes the first byte; remaining bytes are zero.
|
|
||||||
func newTestManager(spkFirstByte byte, mock *mockServer) *Manager {
|
|
||||||
spk := make([]byte, 32)
|
|
||||||
spk[0] = spkFirstByte
|
|
||||||
return &Manager{
|
|
||||||
ifaceName: "wt0",
|
|
||||||
spk: spk,
|
|
||||||
ssk: make([]byte, 32),
|
|
||||||
rpKeyHash: "test-hash",
|
|
||||||
rpPeerIDs: make(map[string]*rp.PeerID),
|
|
||||||
rpWgHandler: NewNetbirdHandler(),
|
|
||||||
server: mock,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validWGKey returns a deterministic 32-byte wireguard public key (base64).
|
|
||||||
func validWGKey(t *testing.T, lastByte byte) string {
|
|
||||||
t.Helper()
|
|
||||||
var k wgtypes.Key
|
|
||||||
k[31] = lastByte
|
|
||||||
return k.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- pure helpers ----------------------------------------------------------
|
|
||||||
|
|
||||||
func TestHashRosenpassKey_Deterministic(t *testing.T) {
|
|
||||||
key := []byte("hello-rosenpass")
|
|
||||||
require.Equal(t, hashRosenpassKey(key), hashRosenpassKey(key))
|
|
||||||
require.Len(t, hashRosenpassKey(key), 64) // sha256 hex
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHashRosenpassKey_DifferentInputsDifferOutputs(t *testing.T) {
|
|
||||||
require.NotEqual(t, hashRosenpassKey([]byte("a")), hashRosenpassKey([]byte("b")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetLogLevel_DefaultWhenUnset(t *testing.T) {
|
|
||||||
// Snapshot + unset to exercise the LookupEnv ok=false branch. t.Setenv
|
|
||||||
// can only set, not delete, so do it manually with restore via t.Cleanup.
|
|
||||||
prev, hadPrev := os.LookupEnv(defaultLogLevelVar)
|
|
||||||
require.NoError(t, os.Unsetenv(defaultLogLevelVar))
|
|
||||||
t.Cleanup(func() {
|
|
||||||
if hadPrev {
|
|
||||||
_ = os.Setenv(defaultLogLevelVar, prev)
|
|
||||||
} else {
|
|
||||||
_ = os.Unsetenv(defaultLogLevelVar)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
require.Equal(t, defaultLog.String(), getLogLevel().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetLogLevel_Cases(t *testing.T) {
|
|
||||||
cases := map[string]string{
|
|
||||||
"debug": "DEBUG",
|
|
||||||
"info": "INFO",
|
|
||||||
"warn": "WARN",
|
|
||||||
"error": "ERROR",
|
|
||||||
"unknown": "INFO", // default fallback
|
|
||||||
}
|
|
||||||
for input, wantStr := range cases {
|
|
||||||
input, wantStr := input, wantStr
|
|
||||||
t.Run(input, func(t *testing.T) {
|
|
||||||
t.Setenv(defaultLogLevelVar, input)
|
|
||||||
require.Equal(t, wantStr, getLogLevel().String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindRandomAvailableUDPPort(t *testing.T) {
|
func TestFindRandomAvailableUDPPort(t *testing.T) {
|
||||||
port, err := findRandomAvailableUDPPort()
|
port, err := findRandomAvailableUDPPort()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Greater(t, port, 0)
|
require.Greater(t, port, 0)
|
||||||
require.LessOrEqual(t, port, 65535)
|
require.LessOrEqual(t, port, 65535)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- addPeer ---------------------------------------------------------------
|
|
||||||
|
|
||||||
func TestAddPeer_HigherLocalPubkey_SetsEndpoint(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0xFF, srv) // local spk lexicographically larger
|
|
||||||
|
|
||||||
remotePubKey := make([]byte, 32) // remote spk = all zeros (smaller)
|
|
||||||
err := m.addPeer(remotePubKey, "rosenpass-host:7000", "100.1.1.1", validWGKey(t, 1))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, srv.addCalls, 1)
|
|
||||||
|
|
||||||
ep := srv.addCalls[0].cfg.Endpoint
|
|
||||||
require.NotNil(t, ep, "initiator side must set Endpoint")
|
|
||||||
require.Equal(t, 7000, ep.Port)
|
|
||||||
require.Equal(t, "100.1.1.1", ep.IP.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddPeer_HigherLocalPubkey_EndpointIPIsIPv4Mapped(t *testing.T) {
|
|
||||||
// Regression guard for the EDESTADDRREQ fix: Endpoint.IP must be 16-byte
|
|
||||||
// (IPv4-mapped IPv6) so it matches the AF_INET6 listening socket family.
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0xFF, srv)
|
|
||||||
|
|
||||||
err := m.addPeer(make([]byte, 32), "rp:5000", "100.1.1.1", validWGKey(t, 1))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ep := srv.addCalls[0].cfg.Endpoint
|
|
||||||
require.NotNil(t, ep)
|
|
||||||
require.Len(t, ep.IP, 16, "IPv4 endpoint must be normalized to 16-byte v4-mapped form")
|
|
||||||
require.True(t, ep.IP.To4() != nil, "Endpoint must still be detected as IPv4")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddPeer_LowerLocalPubkey_LeavesEndpointNil(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0x00, srv) // local spk smaller
|
|
||||||
|
|
||||||
remotePubKey := make([]byte, 32)
|
|
||||||
remotePubKey[0] = 0xFF
|
|
||||||
err := m.addPeer(remotePubKey, "rp:5000", "100.1.1.1", validWGKey(t, 2))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Nil(t, srv.addCalls[0].cfg.Endpoint, "responder side must NOT set Endpoint")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddPeer_PresharedKeyPropagated(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
psk := &wgtypes.Key{0x42}
|
|
||||||
m := newTestManager(0xFF, srv)
|
|
||||||
m.preSharedKey = (*[32]byte)(psk)
|
|
||||||
|
|
||||||
err := m.addPeer(make([]byte, 32), "rp:5000", "100.1.1.1", validWGKey(t, 3))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, [32]byte(*psk), [32]byte(srv.addCalls[0].cfg.PresharedKey))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddPeer_InvalidRosenpassAddr_ReturnsError(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0xFF, srv) // initiator path → parses rosenpassAddr
|
|
||||||
|
|
||||||
err := m.addPeer(make([]byte, 32), "not-a-host-port", "100.1.1.1", validWGKey(t, 1))
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Empty(t, srv.addCalls, "server.AddPeer must not run when address parse fails")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddPeer_InvalidWireGuardPubKey_ReturnsError(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0xFF, srv)
|
|
||||||
|
|
||||||
err := m.addPeer(make([]byte, 32), "rp:5000", "100.1.1.1", "not-a-valid-key")
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddPeer_ServerError_Propagates(t *testing.T) {
|
|
||||||
srv := &mockServer{addErr: errors.New("boom")}
|
|
||||||
m := newTestManager(0xFF, srv)
|
|
||||||
|
|
||||||
err := m.addPeer(make([]byte, 32), "rp:5000", "100.1.1.1", validWGKey(t, 1))
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regression guard for issue #4341 (Android crash). If Run() has not completed
|
|
||||||
// before OnConnected fires, m.rpWgHandler or m.server may be nil. Without the
|
|
||||||
// nil guards, m.rpWgHandler.AddPeer panics on nil receiver.
|
|
||||||
func TestAddPeer_NilHandler_ReturnsErrorNoCrash(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0xFF, srv)
|
|
||||||
m.rpWgHandler = nil // simulate Run() not yet completed
|
|
||||||
|
|
||||||
err := m.addPeer(make([]byte, 32), "rp:5000", "100.1.1.1", validWGKey(t, 1))
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Contains(t, err.Error(), "wg handler not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddPeer_NilServer_ReturnsErrorNoCrash(t *testing.T) {
|
|
||||||
m := newTestManager(0xFF, nil)
|
|
||||||
m.server = nil // simulate Run() not yet completed
|
|
||||||
|
|
||||||
err := m.addPeer(make([]byte, 32), "rp:5000", "100.1.1.1", validWGKey(t, 1))
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Contains(t, err.Error(), "server not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewManager must pre-initialize rpWgHandler so the nil-receiver crash from
|
|
||||||
// issue #4341 cannot occur in the window between NewManager and Run().
|
|
||||||
func TestNewManager_PreInitializesHandler(t *testing.T) {
|
|
||||||
psk := wgtypes.Key{}
|
|
||||||
m, err := NewManager(&psk, "wt0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, m.rpWgHandler, "rpWgHandler must be initialized in NewManager")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddPeer_RecordsPeerID(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0xFF, srv)
|
|
||||||
|
|
||||||
wgKey := validWGKey(t, 5)
|
|
||||||
err := m.addPeer(make([]byte, 32), "rp:5000", "100.1.1.1", wgKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Contains(t, m.rpPeerIDs, wgKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- OnConnected / OnDisconnected ------------------------------------------
|
|
||||||
|
|
||||||
func TestOnConnected_NilRemotePubKey_NoAddPeer(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0xFF, srv)
|
|
||||||
|
|
||||||
m.OnConnected(validWGKey(t, 1), nil, "100.1.1.1", "rp:5000")
|
|
||||||
require.Empty(t, srv.addCalls, "nil remote rosenpass pubkey must skip AddPeer")
|
|
||||||
require.Empty(t, m.rpPeerIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOnConnected_ValidPubKey_CallsAddPeer(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0xFF, srv)
|
|
||||||
|
|
||||||
wgKey := validWGKey(t, 1)
|
|
||||||
m.OnConnected(wgKey, make([]byte, 32), "100.1.1.1", "rp:5000")
|
|
||||||
require.Len(t, srv.addCalls, 1)
|
|
||||||
require.Contains(t, m.rpPeerIDs, wgKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOnDisconnected_UnknownPeer_NoOp(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0xFF, srv)
|
|
||||||
|
|
||||||
m.OnDisconnected(validWGKey(t, 99))
|
|
||||||
require.Empty(t, srv.removed, "unknown peer key must not call RemovePeer")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOnDisconnected_KnownPeer_CallsRemoveAndForgets(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0xFF, srv)
|
|
||||||
|
|
||||||
wgKey := validWGKey(t, 1)
|
|
||||||
require.NoError(t, m.addPeer(make([]byte, 32), "rp:5000", "100.1.1.1", wgKey))
|
|
||||||
require.Contains(t, m.rpPeerIDs, wgKey)
|
|
||||||
|
|
||||||
m.OnDisconnected(wgKey)
|
|
||||||
require.Len(t, srv.removed, 1)
|
|
||||||
require.NotContains(t, m.rpPeerIDs, wgKey, "peer must be forgotten after disconnect")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- IsPresharedKeyInitialized ---------------------------------------------
|
|
||||||
|
|
||||||
func TestIsPresharedKeyInitialized_UnknownPeer_ReturnsFalse(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0xFF, srv)
|
|
||||||
require.False(t, m.IsPresharedKeyInitialized(validWGKey(t, 1)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsPresharedKeyInitialized_AddedButNotHandshaken_ReturnsFalse(t *testing.T) {
|
|
||||||
srv := &mockServer{}
|
|
||||||
m := newTestManager(0xFF, srv)
|
|
||||||
|
|
||||||
wgKey := validWGKey(t, 2)
|
|
||||||
require.NoError(t, m.addPeer(make([]byte, 32), "rp:5000", "100.1.1.1", wgKey))
|
|
||||||
require.False(t, m.IsPresharedKeyInitialized(wgKey))
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- NetbirdHandler.outputKey ----------------------------------------------
|
|
||||||
|
|
||||||
func TestHandler_OutputKey_FirstCallUsesUpdateOnlyFalse(t *testing.T) {
|
|
||||||
h := NewNetbirdHandler()
|
|
||||||
iface := &mockIface{}
|
|
||||||
h.SetInterface(iface)
|
|
||||||
|
|
||||||
pid := rp.PeerID{0x01}
|
|
||||||
wgKey := wgtypes.Key{0xAA}
|
|
||||||
h.AddPeer(pid, "wt0", rp.Key(wgKey))
|
|
||||||
|
|
||||||
psk := rp.Key{0xBB}
|
|
||||||
h.HandshakeCompleted(pid, psk)
|
|
||||||
|
|
||||||
require.Len(t, iface.calls, 1)
|
|
||||||
require.False(t, iface.calls[0].updateOnly, "first PSK rotation must use updateOnly=false")
|
|
||||||
require.Equal(t, wgKey.String(), iface.calls[0].peerKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandler_OutputKey_SubsequentCallsUseUpdateOnlyTrue(t *testing.T) {
|
|
||||||
h := NewNetbirdHandler()
|
|
||||||
iface := &mockIface{}
|
|
||||||
h.SetInterface(iface)
|
|
||||||
|
|
||||||
pid := rp.PeerID{0x02}
|
|
||||||
h.AddPeer(pid, "wt0", rp.Key(wgtypes.Key{0xCC}))
|
|
||||||
|
|
||||||
h.HandshakeCompleted(pid, rp.Key{0x01}) // first
|
|
||||||
h.HandshakeCompleted(pid, rp.Key{0x02}) // second
|
|
||||||
|
|
||||||
require.Len(t, iface.calls, 2)
|
|
||||||
require.False(t, iface.calls[0].updateOnly)
|
|
||||||
require.True(t, iface.calls[1].updateOnly, "subsequent rotations must use updateOnly=true")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandler_OutputKey_NilInterface_NoCrashNoCall(t *testing.T) {
|
|
||||||
h := NewNetbirdHandler()
|
|
||||||
// no SetInterface — iface remains nil
|
|
||||||
pid := rp.PeerID{0x03}
|
|
||||||
h.AddPeer(pid, "wt0", rp.Key(wgtypes.Key{}))
|
|
||||||
|
|
||||||
// Must not panic.
|
|
||||||
h.HandshakeCompleted(pid, rp.Key{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandler_OutputKey_UnknownPeer_NoCall(t *testing.T) {
|
|
||||||
h := NewNetbirdHandler()
|
|
||||||
iface := &mockIface{}
|
|
||||||
h.SetInterface(iface)
|
|
||||||
|
|
||||||
h.HandshakeCompleted(rp.PeerID{0xFF}, rp.Key{})
|
|
||||||
require.Empty(t, iface.calls, "unknown peer id must not trigger SetPresharedKey")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandler_RemovePeer_ClearsInitializedState(t *testing.T) {
|
|
||||||
h := NewNetbirdHandler()
|
|
||||||
iface := &mockIface{}
|
|
||||||
h.SetInterface(iface)
|
|
||||||
|
|
||||||
pid := rp.PeerID{0x04}
|
|
||||||
h.AddPeer(pid, "wt0", rp.Key(wgtypes.Key{0xDD}))
|
|
||||||
h.HandshakeCompleted(pid, rp.Key{0x01})
|
|
||||||
require.True(t, h.IsPeerInitialized(pid))
|
|
||||||
|
|
||||||
h.RemovePeer(pid)
|
|
||||||
require.False(t, h.IsPeerInitialized(pid), "RemovePeer must clear initialized flag")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandler_SetInterfaceAfterAddPeer_StillReceivesKey(t *testing.T) {
|
|
||||||
h := NewNetbirdHandler()
|
|
||||||
pid := rp.PeerID{0x05}
|
|
||||||
wgKey := wgtypes.Key{0xEE}
|
|
||||||
h.AddPeer(pid, "wt0", rp.Key(wgKey))
|
|
||||||
|
|
||||||
iface := &mockIface{}
|
|
||||||
h.SetInterface(iface) // set after AddPeer
|
|
||||||
|
|
||||||
h.HandshakeCompleted(pid, rp.Key{0x42})
|
|
||||||
require.Len(t, iface.calls, 1)
|
|
||||||
require.Equal(t, wgKey.String(), iface.calls[0].peerKey)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package rosenpass
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeterministicSeedKey derives a 32-byte WireGuard preshared key from a pair
|
|
||||||
// of peer public keys. Both peers, given the same key pair, produce the same
|
|
||||||
// output regardless of which side runs the function: the inputs are ordered
|
|
||||||
// lexicographically before concatenation.
|
|
||||||
//
|
|
||||||
// NetBird uses this value as the initial Rosenpass-side preshared key when no
|
|
||||||
// explicit account-level PSK is configured, so both peers converge on the same
|
|
||||||
// PSK before the first post-quantum handshake completes.
|
|
||||||
//
|
|
||||||
// The resulting key MUST NOT be treated as quantum-safe: it is deterministic
|
|
||||||
// from public keys and exists only to seed WireGuard until Rosenpass rotates
|
|
||||||
// in a real post-quantum PSK.
|
|
||||||
func DeterministicSeedKey(localKey, remoteKey string) (*wgtypes.Key, error) {
|
|
||||||
lk := []byte(localKey)
|
|
||||||
rk := []byte(remoteKey)
|
|
||||||
if len(lk) < 16 || len(rk) < 16 {
|
|
||||||
return nil, fmt.Errorf("rosenpass: peer keys must be at least 16 bytes (got local=%d, remote=%d)", len(lk), len(rk))
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyInput []byte
|
|
||||||
if localKey > remoteKey {
|
|
||||||
keyInput = append(keyInput, lk[:16]...)
|
|
||||||
keyInput = append(keyInput, rk[:16]...)
|
|
||||||
} else {
|
|
||||||
keyInput = append(keyInput, rk[:16]...)
|
|
||||||
keyInput = append(keyInput, lk[:16]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := wgtypes.NewKey(keyInput)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("rosenpass: deterministic seed key: %w", err)
|
|
||||||
}
|
|
||||||
return &key, nil
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package rosenpass
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDeterministicSeedKey_SameForBothSides(t *testing.T) {
|
|
||||||
// Peer A and peer B must derive the same PSK regardless of which side
|
|
||||||
// computes it: the function orders inputs internally.
|
|
||||||
a := strings.Repeat("a", 32)
|
|
||||||
b := strings.Repeat("b", 32)
|
|
||||||
|
|
||||||
keyAB, err := DeterministicSeedKey(a, b)
|
|
||||||
require.NoError(t, err)
|
|
||||||
keyBA, err := DeterministicSeedKey(b, a)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, keyAB.String(), keyBA.String(), "swapping arguments must yield identical key")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeterministicSeedKey_ChangesWithKeys(t *testing.T) {
|
|
||||||
a := strings.Repeat("a", 32)
|
|
||||||
b := strings.Repeat("b", 32)
|
|
||||||
c := strings.Repeat("c", 32)
|
|
||||||
|
|
||||||
keyAB, err := DeterministicSeedKey(a, b)
|
|
||||||
require.NoError(t, err)
|
|
||||||
keyAC, err := DeterministicSeedKey(a, c)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotEqual(t, keyAB.String(), keyAC.String(), "different peer pair must yield different key")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeterministicSeedKey_TooShortKey_ReturnsError(t *testing.T) {
|
|
||||||
short := "short" // < 16 bytes
|
|
||||||
long := strings.Repeat("x", 32)
|
|
||||||
|
|
||||||
_, err := DeterministicSeedKey(short, long)
|
|
||||||
require.Error(t, err)
|
|
||||||
_, err = DeterministicSeedKey(long, short)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
//go:build dragonfly || freebsd || netbsd || openbsd
|
|
||||||
|
|
||||||
package systemops
|
|
||||||
|
|
||||||
// IgnoreAddedDefaultRoute reports whether an RTM_ADD default route with the
|
|
||||||
// given flags should be ignored by the network monitor.
|
|
||||||
func IgnoreAddedDefaultRoute(flags int) bool {
|
|
||||||
return filterRoutesByFlags(flags)
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package systemops
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
// IgnoreAddedDefaultRoute reports whether an RTM_ADD default route with the
|
|
||||||
// given flags should be ignored by the network monitor. Scoped routes
|
|
||||||
// (RTF_IFSCOPE) are tied to a specific interface index and cannot replace the
|
|
||||||
// unscoped default the kernel uses for general egress, so flapping ones (e.g.
|
|
||||||
// Wi-Fi calling IMS tunnels on ipsec0, Docker bridges, scoped utun defaults)
|
|
||||||
// must not trigger an engine restart.
|
|
||||||
func IgnoreAddedDefaultRoute(flags int) bool {
|
|
||||||
if filterRoutesByFlags(flags) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if flags&unix.RTF_IFSCOPE != 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -96,19 +96,17 @@ func (m *Manager) Stop(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
cancel := m.cancel
|
defer m.mu.Unlock()
|
||||||
done := m.done
|
|
||||||
m.mu.Unlock()
|
|
||||||
|
|
||||||
if cancel == nil {
|
if m.cancel == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cancel()
|
m.cancel()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
case <-done:
|
case <-m.done:
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -64,6 +64,13 @@
|
|||||||
<RegistryValue Name="InstalledByMSI" Type="integer" Value="1" KeyPath="yes" />
|
<RegistryValue Name="InstalledByMSI" Type="integer" Value="1" KeyPath="yes" />
|
||||||
</RegistryKey>
|
</RegistryKey>
|
||||||
</Component>
|
</Component>
|
||||||
|
<!-- Drop the HKCU Run\Netbird value written by legacy NSIS installers. -->
|
||||||
|
<Component Id="NetbirdLegacyHKCUCleanup" Guid="*">
|
||||||
|
<RegistryValue Root="HKCU" Key="Software\NetBird GmbH\Installer"
|
||||||
|
Name="LegacyHKCUCleanup" Type="integer" Value="1" KeyPath="yes" />
|
||||||
|
<RemoveRegistryValue Root="HKCU"
|
||||||
|
Key="Software\Microsoft\Windows\CurrentVersion\Run" Name="Netbird" />
|
||||||
|
</Component>
|
||||||
</StandardDirectory>
|
</StandardDirectory>
|
||||||
|
|
||||||
<StandardDirectory Id="CommonAppDataFolder">
|
<StandardDirectory Id="CommonAppDataFolder">
|
||||||
@@ -76,10 +83,28 @@
|
|||||||
</Directory>
|
</Directory>
|
||||||
</StandardDirectory>
|
</StandardDirectory>
|
||||||
|
|
||||||
|
<!-- Drop Run, App Paths and Uninstall entries written by legacy NSIS
|
||||||
|
installers into the 32-bit registry view (HKLM\Software\Wow6432Node). -->
|
||||||
|
<Component Id="NetbirdLegacyWow6432Cleanup" Directory="NetbirdInstallDir"
|
||||||
|
Guid="bda5d628-16bd-4086-b2c1-5099d8d51763" Bitness="always32">
|
||||||
|
<RegistryValue Root="HKLM" Key="Software\NetBird GmbH\Installer"
|
||||||
|
Name="LegacyWow6432Cleanup" Type="integer" Value="1" KeyPath="yes" />
|
||||||
|
<RemoveRegistryValue Root="HKLM"
|
||||||
|
Key="Software\Microsoft\Windows\CurrentVersion\Run" Name="Netbird" />
|
||||||
|
<RemoveRegistryKey Action="removeOnInstall" Root="HKLM"
|
||||||
|
Key="Software\Microsoft\Windows\CurrentVersion\App Paths\Netbird" />
|
||||||
|
<RemoveRegistryKey Action="removeOnInstall" Root="HKLM"
|
||||||
|
Key="Software\Microsoft\Windows\CurrentVersion\App Paths\Netbird-ui" />
|
||||||
|
<RemoveRegistryKey Action="removeOnInstall" Root="HKLM"
|
||||||
|
Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\Netbird" />
|
||||||
|
</Component>
|
||||||
|
|
||||||
<ComponentGroup Id="NetbirdFilesComponent">
|
<ComponentGroup Id="NetbirdFilesComponent">
|
||||||
<ComponentRef Id="NetbirdFiles" />
|
<ComponentRef Id="NetbirdFiles" />
|
||||||
<ComponentRef Id="NetbirdAumidRegistry" />
|
<ComponentRef Id="NetbirdAumidRegistry" />
|
||||||
<ComponentRef Id="NetbirdAutoStart" />
|
<ComponentRef Id="NetbirdAutoStart" />
|
||||||
|
<ComponentRef Id="NetbirdLegacyHKCUCleanup" />
|
||||||
|
<ComponentRef Id="NetbirdLegacyWow6432Cleanup" />
|
||||||
</ComponentGroup>
|
</ComponentGroup>
|
||||||
|
|
||||||
<util:CloseApplication Id="CloseNetBird" CloseMessage="no" Target="netbird.exe" RebootPrompt="no" />
|
<util:CloseApplication Id="CloseNetBird" CloseMessage="no" Target="netbird.exe" RebootPrompt="no" />
|
||||||
|
|||||||
@@ -9,9 +9,21 @@ then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
old_pwd=$(pwd)
|
old_pwd=$(pwd)
|
||||||
script_path=$(dirname $(realpath "$0"))
|
script_path=$(dirname "$(realpath "$0")")
|
||||||
cd "$script_path"
|
cd "$script_path"
|
||||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.6
|
|
||||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
|
repo_root=$(git rev-parse --show-toplevel)
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$repo_root/proto-tools.env"
|
||||||
|
|
||||||
|
actual_protoc=$(protoc --version | awk '{print $2}')
|
||||||
|
if [[ "$actual_protoc" != "$PROTOC_VERSION" ]]; then
|
||||||
|
echo "ERROR: protoc version $actual_protoc differs from pinned $PROTOC_VERSION" >&2
|
||||||
|
echo "Install protoc $PROTOC_VERSION from https://github.com/protocolbuffers/protobuf/releases" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
go install "google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VERSION}"
|
||||||
|
go install "google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION}"
|
||||||
protoc -I ./ ./daemon.proto --go_out=../ --go-grpc_out=../ --experimental_allow_proto3_optional
|
protoc -I ./ ./daemon.proto --go_out=../ --go-grpc_out=../ --experimental_allow_proto3_optional
|
||||||
cd "$old_pwd"
|
cd "$old_pwd"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator/validator"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
||||||
@@ -315,7 +315,7 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
ia, _ := validator.NewIntegratedValidator(context.Background(), peersManager, settingsManagerMock, eventStore, cacheStore)
|
ia, _ := integrations.NewIntegratedValidator(context.Background(), peersManager, settingsManagerMock, eventStore, cacheStore)
|
||||||
|
|
||||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -3,14 +3,15 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/zcalusic/sysinfo"
|
"github.com/zcalusic/sysinfo"
|
||||||
|
|
||||||
@@ -28,11 +29,19 @@ func UpdateStaticInfoAsync() {
|
|||||||
|
|
||||||
// GetInfo retrieves and parses the system information
|
// GetInfo retrieves and parses the system information
|
||||||
func GetInfo(ctx context.Context) *Info {
|
func GetInfo(ctx context.Context) *Info {
|
||||||
kernelName, kernelVersion, kernelPlatform := kernelInfo()
|
info := _getInfo()
|
||||||
|
for strings.Contains(info, "broken pipe") {
|
||||||
|
info = _getInfo()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
osStr := strings.ReplaceAll(info, "\n", "")
|
||||||
|
osStr = strings.ReplaceAll(osStr, "\r\n", "")
|
||||||
|
osInfo := strings.Split(osStr, " ")
|
||||||
|
|
||||||
osName, osVersion := readOsReleaseFile()
|
osName, osVersion := readOsReleaseFile()
|
||||||
if osName == "" {
|
if osName == "" {
|
||||||
osName = kernelName
|
osName = osInfo[3]
|
||||||
}
|
}
|
||||||
|
|
||||||
systemHostname, _ := os.Hostname()
|
systemHostname, _ := os.Hostname()
|
||||||
@@ -49,8 +58,8 @@ func GetInfo(ctx context.Context) *Info {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gio := &Info{
|
gio := &Info{
|
||||||
Kernel: kernelName,
|
Kernel: osInfo[0],
|
||||||
Platform: kernelPlatform,
|
Platform: osInfo[2],
|
||||||
OS: osName,
|
OS: osName,
|
||||||
OSVersion: osVersion,
|
OSVersion: osVersion,
|
||||||
Hostname: extractDeviceName(ctx, systemHostname),
|
Hostname: extractDeviceName(ctx, systemHostname),
|
||||||
@@ -58,7 +67,7 @@ func GetInfo(ctx context.Context) *Info {
|
|||||||
CPUs: runtime.NumCPU(),
|
CPUs: runtime.NumCPU(),
|
||||||
NetbirdVersion: version.NetbirdVersion(),
|
NetbirdVersion: version.NetbirdVersion(),
|
||||||
UIVersion: extractUserAgent(ctx),
|
UIVersion: extractUserAgent(ctx),
|
||||||
KernelVersion: kernelVersion,
|
KernelVersion: osInfo[1],
|
||||||
NetworkAddresses: addrs,
|
NetworkAddresses: addrs,
|
||||||
SystemSerialNumber: si.SystemSerialNumber,
|
SystemSerialNumber: si.SystemSerialNumber,
|
||||||
SystemProductName: si.SystemProductName,
|
SystemProductName: si.SystemProductName,
|
||||||
@@ -69,12 +78,18 @@ func GetInfo(ctx context.Context) *Info {
|
|||||||
return gio
|
return gio
|
||||||
}
|
}
|
||||||
|
|
||||||
func kernelInfo() (string, string, string) {
|
func _getInfo() string {
|
||||||
var uts unix.Utsname
|
cmd := exec.Command("uname", "-srio")
|
||||||
if err := unix.Uname(&uts); err != nil {
|
cmd.Stdin = strings.NewReader("some")
|
||||||
return "", "", ""
|
var out bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("getInfo: %s", err)
|
||||||
}
|
}
|
||||||
return unix.ByteSliceToString(uts.Sysname[:]), unix.ByteSliceToString(uts.Release[:]), unix.ByteSliceToString(uts.Machine[:])
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func sysInfo() (string, string, string) {
|
func sysInfo() (string, string, string) {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
certValidationTimeout = 5 * time.Minute
|
certValidationTimeout = 60 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *RDCleanPathProxy) validateCertificateWithJS(conn *proxyConnection, certChain [][]byte) (bool, error) {
|
func (p *RDCleanPathProxy) validateCertificateWithJS(conn *proxyConnection, certChain [][]byte) (bool, error) {
|
||||||
@@ -47,31 +46,17 @@ func (p *RDCleanPathProxy) validateCertificateWithJS(conn *proxyConnection, cert
|
|||||||
|
|
||||||
promise := conn.wsHandlers.Call("onCertificateRequest", certInfo)
|
promise := conn.wsHandlers.Call("onCertificateRequest", certInfo)
|
||||||
|
|
||||||
resultChan := make(chan bool, 1)
|
resultChan := make(chan bool)
|
||||||
errorChan := make(chan error, 1)
|
errorChan := make(chan error)
|
||||||
|
|
||||||
// Release from inside the callbacks so a post-timeout promise resolution
|
promise.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
// does not invoke an already-released func.
|
result := args[0].Bool()
|
||||||
var thenFn, catchFn js.Func
|
resultChan <- result
|
||||||
var releaseOnce sync.Once
|
|
||||||
release := func() {
|
|
||||||
releaseOnce.Do(func() {
|
|
||||||
thenFn.Release()
|
|
||||||
catchFn.Release()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
thenFn = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
||||||
defer release()
|
|
||||||
resultChan <- args[0].Bool()
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})).Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
catchFn = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
||||||
defer release()
|
|
||||||
errorChan <- fmt.Errorf("certificate validation failed")
|
errorChan <- fmt.Errorf("certificate validation failed")
|
||||||
return nil
|
return nil
|
||||||
})
|
}))
|
||||||
|
|
||||||
promise.Call("then", thenFn).Call("catch", catchFn)
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case result := <-resultChan:
|
case result := <-resultChan:
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -58,8 +57,6 @@ type RDCleanPathProxy struct {
|
|||||||
}
|
}
|
||||||
activeConnections map[string]*proxyConnection
|
activeConnections map[string]*proxyConnection
|
||||||
destinations map[string]string
|
destinations map[string]string
|
||||||
pendingHandlers map[string]js.Func
|
|
||||||
nextID atomic.Uint64
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,15 +66,8 @@ type proxyConnection struct {
|
|||||||
rdpConn net.Conn
|
rdpConn net.Conn
|
||||||
tlsConn *tls.Conn
|
tlsConn *tls.Conn
|
||||||
wsHandlers js.Value
|
wsHandlers js.Value
|
||||||
// Go-side callbacks exposed to JS. js.FuncOf pins the Go closure in a
|
ctx context.Context
|
||||||
// global handle map and MUST be released, otherwise every connection
|
cancel context.CancelFunc
|
||||||
// leaks the Go memory the closure captures.
|
|
||||||
wsHandlerFn js.Func
|
|
||||||
onMessageFn js.Func
|
|
||||||
onCloseFn js.Func
|
|
||||||
cleanupOnce sync.Once
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRDCleanPathProxy creates a new RDCleanPath proxy
|
// NewRDCleanPathProxy creates a new RDCleanPath proxy
|
||||||
@@ -90,11 +80,7 @@ func NewRDCleanPathProxy(client interface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateProxy creates a new proxy endpoint for the given destination.
|
// CreateProxy creates a new proxy endpoint for the given destination
|
||||||
// The registered handler fn and its destinations/pendingHandlers entries are
|
|
||||||
// only released once a connection is established and cleanupConnection runs.
|
|
||||||
// If a caller invokes CreateProxy but never connects to the returned URL,
|
|
||||||
// those entries stay pinned for the lifetime of the page.
|
|
||||||
func (p *RDCleanPathProxy) CreateProxy(hostname, port string) js.Value {
|
func (p *RDCleanPathProxy) CreateProxy(hostname, port string) js.Value {
|
||||||
destination := net.JoinHostPort(hostname, port)
|
destination := net.JoinHostPort(hostname, port)
|
||||||
|
|
||||||
@@ -102,7 +88,7 @@ func (p *RDCleanPathProxy) CreateProxy(hostname, port string) js.Value {
|
|||||||
resolve := args[0]
|
resolve := args[0]
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
proxyID := fmt.Sprintf("proxy_%d", p.nextID.Add(1))
|
proxyID := fmt.Sprintf("proxy_%d", len(p.activeConnections))
|
||||||
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
if p.destinations == nil {
|
if p.destinations == nil {
|
||||||
@@ -114,7 +100,7 @@ func (p *RDCleanPathProxy) CreateProxy(hostname, port string) js.Value {
|
|||||||
proxyURL := fmt.Sprintf("%s://%s/%s", RDCleanPathProxyScheme, RDCleanPathProxyHost, proxyID)
|
proxyURL := fmt.Sprintf("%s://%s/%s", RDCleanPathProxyScheme, RDCleanPathProxyHost, proxyID)
|
||||||
|
|
||||||
// Register the WebSocket handler for this specific proxy
|
// Register the WebSocket handler for this specific proxy
|
||||||
handlerFn := js.FuncOf(func(_ js.Value, args []js.Value) any {
|
js.Global().Set(fmt.Sprintf("handleRDCleanPathWebSocket_%s", proxyID), js.FuncOf(func(_ js.Value, args []js.Value) any {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return js.ValueOf("error: requires WebSocket argument")
|
return js.ValueOf("error: requires WebSocket argument")
|
||||||
}
|
}
|
||||||
@@ -122,14 +108,7 @@ func (p *RDCleanPathProxy) CreateProxy(hostname, port string) js.Value {
|
|||||||
ws := args[0]
|
ws := args[0]
|
||||||
p.HandleWebSocketConnection(ws, proxyID)
|
p.HandleWebSocketConnection(ws, proxyID)
|
||||||
return nil
|
return nil
|
||||||
})
|
}))
|
||||||
p.mu.Lock()
|
|
||||||
if p.pendingHandlers == nil {
|
|
||||||
p.pendingHandlers = make(map[string]js.Func)
|
|
||||||
}
|
|
||||||
p.pendingHandlers[proxyID] = handlerFn
|
|
||||||
p.mu.Unlock()
|
|
||||||
js.Global().Set(fmt.Sprintf("handleRDCleanPathWebSocket_%s", proxyID), handlerFn)
|
|
||||||
|
|
||||||
log.Infof("Created RDCleanPath proxy endpoint: %s for destination: %s", proxyURL, destination)
|
log.Infof("Created RDCleanPath proxy endpoint: %s for destination: %s", proxyURL, destination)
|
||||||
resolve.Invoke(proxyURL)
|
resolve.Invoke(proxyURL)
|
||||||
@@ -163,10 +142,6 @@ func (p *RDCleanPathProxy) HandleWebSocketConnection(ws js.Value, proxyID string
|
|||||||
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
p.activeConnections[proxyID] = conn
|
p.activeConnections[proxyID] = conn
|
||||||
if fn, ok := p.pendingHandlers[proxyID]; ok {
|
|
||||||
conn.wsHandlerFn = fn
|
|
||||||
delete(p.pendingHandlers, proxyID)
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
|
|
||||||
p.setupWebSocketHandlers(ws, conn)
|
p.setupWebSocketHandlers(ws, conn)
|
||||||
@@ -175,7 +150,7 @@ func (p *RDCleanPathProxy) HandleWebSocketConnection(ws js.Value, proxyID string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *RDCleanPathProxy) setupWebSocketHandlers(ws js.Value, conn *proxyConnection) {
|
func (p *RDCleanPathProxy) setupWebSocketHandlers(ws js.Value, conn *proxyConnection) {
|
||||||
conn.onMessageFn = js.FuncOf(func(this js.Value, args []js.Value) any {
|
ws.Set("onGoMessage", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -183,15 +158,13 @@ func (p *RDCleanPathProxy) setupWebSocketHandlers(ws js.Value, conn *proxyConnec
|
|||||||
data := args[0]
|
data := args[0]
|
||||||
go p.handleWebSocketMessage(conn, data)
|
go p.handleWebSocketMessage(conn, data)
|
||||||
return nil
|
return nil
|
||||||
})
|
}))
|
||||||
ws.Set("onGoMessage", conn.onMessageFn)
|
|
||||||
|
|
||||||
conn.onCloseFn = js.FuncOf(func(_ js.Value, args []js.Value) any {
|
ws.Set("onGoClose", js.FuncOf(func(_ js.Value, args []js.Value) any {
|
||||||
log.Debug("WebSocket closed by JavaScript")
|
log.Debug("WebSocket closed by JavaScript")
|
||||||
conn.cancel()
|
conn.cancel()
|
||||||
return nil
|
return nil
|
||||||
})
|
}))
|
||||||
ws.Set("onGoClose", conn.onCloseFn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RDCleanPathProxy) handleWebSocketMessage(conn *proxyConnection, data js.Value) {
|
func (p *RDCleanPathProxy) handleWebSocketMessage(conn *proxyConnection, data js.Value) {
|
||||||
@@ -288,49 +261,25 @@ func (p *RDCleanPathProxy) handleDirectRDP(conn *proxyConnection, firstPacket []
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *RDCleanPathProxy) cleanupConnection(conn *proxyConnection) {
|
func (p *RDCleanPathProxy) cleanupConnection(conn *proxyConnection) {
|
||||||
conn.cleanupOnce.Do(func() {
|
log.Debugf("Cleaning up connection %s", conn.id)
|
||||||
log.Debugf("Cleaning up connection %s", conn.id)
|
conn.cancel()
|
||||||
conn.cancel()
|
if conn.tlsConn != nil {
|
||||||
if conn.tlsConn != nil {
|
log.Debug("Closing TLS connection")
|
||||||
log.Debug("Closing TLS connection")
|
if err := conn.tlsConn.Close(); err != nil {
|
||||||
if err := conn.tlsConn.Close(); err != nil {
|
log.Debugf("Error closing TLS connection: %v", err)
|
||||||
log.Debugf("Error closing TLS connection: %v", err)
|
|
||||||
}
|
|
||||||
conn.tlsConn = nil
|
|
||||||
}
|
}
|
||||||
if conn.rdpConn != nil {
|
conn.tlsConn = nil
|
||||||
log.Debug("Closing TCP connection")
|
}
|
||||||
if err := conn.rdpConn.Close(); err != nil {
|
if conn.rdpConn != nil {
|
||||||
log.Debugf("Error closing TCP connection: %v", err)
|
log.Debug("Closing TCP connection")
|
||||||
}
|
if err := conn.rdpConn.Close(); err != nil {
|
||||||
conn.rdpConn = nil
|
log.Debugf("Error closing TCP connection: %v", err)
|
||||||
}
|
}
|
||||||
js.Global().Delete(fmt.Sprintf("handleRDCleanPathWebSocket_%s", conn.id))
|
conn.rdpConn = nil
|
||||||
|
}
|
||||||
// Detach before releasing so late JS calls surface as TypeError instead
|
p.mu.Lock()
|
||||||
// of silent "call to released function".
|
delete(p.activeConnections, conn.id)
|
||||||
if conn.wsHandlers.Truthy() {
|
p.mu.Unlock()
|
||||||
conn.wsHandlers.Set("onGoMessage", js.Undefined())
|
|
||||||
conn.wsHandlers.Set("onGoClose", js.Undefined())
|
|
||||||
}
|
|
||||||
|
|
||||||
// wsHandlerFn may be zero-value if the pending handler lookup missed.
|
|
||||||
if conn.wsHandlerFn.Truthy() {
|
|
||||||
conn.wsHandlerFn.Release()
|
|
||||||
}
|
|
||||||
if conn.onMessageFn.Truthy() {
|
|
||||||
conn.onMessageFn.Release()
|
|
||||||
}
|
|
||||||
if conn.onCloseFn.Truthy() {
|
|
||||||
conn.onCloseFn.Release()
|
|
||||||
}
|
|
||||||
|
|
||||||
p.mu.Lock()
|
|
||||||
delete(p.activeConnections, conn.id)
|
|
||||||
delete(p.destinations, conn.id)
|
|
||||||
delete(p.pendingHandlers, conn.id)
|
|
||||||
p.mu.Unlock()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RDCleanPathProxy) sendToWebSocket(conn *proxyConnection, data []byte) {
|
func (p *RDCleanPathProxy) sendToWebSocket(conn *proxyConnection, data []byte) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
func CreateJSInterface(client *Client) js.Value {
|
func CreateJSInterface(client *Client) js.Value {
|
||||||
jsInterface := js.Global().Get("Object").Call("create", js.Null())
|
jsInterface := js.Global().Get("Object").Call("create", js.Null())
|
||||||
|
|
||||||
writeFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
|
jsInterface.Set("write", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return js.ValueOf(false)
|
return js.ValueOf(false)
|
||||||
}
|
}
|
||||||
@@ -32,10 +32,9 @@ func CreateJSInterface(client *Client) js.Value {
|
|||||||
|
|
||||||
_, err := client.Write(bytes)
|
_, err := client.Write(bytes)
|
||||||
return js.ValueOf(err == nil)
|
return js.ValueOf(err == nil)
|
||||||
})
|
}))
|
||||||
jsInterface.Set("write", writeFunc)
|
|
||||||
|
|
||||||
resizeFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
|
jsInterface.Set("resize", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return js.ValueOf(false)
|
return js.ValueOf(false)
|
||||||
}
|
}
|
||||||
@@ -43,26 +42,14 @@ func CreateJSInterface(client *Client) js.Value {
|
|||||||
rows := args[1].Int()
|
rows := args[1].Int()
|
||||||
err := client.Resize(cols, rows)
|
err := client.Resize(cols, rows)
|
||||||
return js.ValueOf(err == nil)
|
return js.ValueOf(err == nil)
|
||||||
})
|
}))
|
||||||
jsInterface.Set("resize", resizeFunc)
|
|
||||||
|
|
||||||
closeFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
|
jsInterface.Set("close", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
client.Close()
|
client.Close()
|
||||||
return js.Undefined()
|
return js.Undefined()
|
||||||
})
|
}))
|
||||||
jsInterface.Set("close", closeFunc)
|
|
||||||
|
|
||||||
go func() {
|
go readLoop(client, jsInterface)
|
||||||
readLoop(client, jsInterface)
|
|
||||||
// Detach before releasing so late JS calls surface as TypeError instead
|
|
||||||
// of silent "call to released function".
|
|
||||||
jsInterface.Set("write", js.Undefined())
|
|
||||||
jsInterface.Set("resize", js.Undefined())
|
|
||||||
jsInterface.Set("close", js.Undefined())
|
|
||||||
writeFunc.Release()
|
|
||||||
resizeFunc.Release()
|
|
||||||
closeFunc.Release()
|
|
||||||
}()
|
|
||||||
|
|
||||||
return jsInterface
|
return jsInterface
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,10 +67,6 @@ func init() {
|
|||||||
rootCmd.AddCommand(newTokenCommands())
|
rootCmd.AddCommand(newTokenCommands())
|
||||||
}
|
}
|
||||||
|
|
||||||
func RootCmd() *cobra.Command {
|
|
||||||
return rootCmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func Execute() error {
|
func Execute() error {
|
||||||
return rootCmd.Execute()
|
return rootCmd.Execute()
|
||||||
}
|
}
|
||||||
@@ -172,7 +168,7 @@ func initializeConfig() error {
|
|||||||
// serverInstances holds all server instances created during startup.
|
// serverInstances holds all server instances created during startup.
|
||||||
type serverInstances struct {
|
type serverInstances struct {
|
||||||
relaySrv *relayServer.Server
|
relaySrv *relayServer.Server
|
||||||
mgmtSrv mgmtServer.Server
|
mgmtSrv *mgmtServer.BaseServer
|
||||||
signalSrv *signalServer.Server
|
signalSrv *signalServer.Server
|
||||||
healthcheck *healthcheck.Server
|
healthcheck *healthcheck.Server
|
||||||
stunServer *stun.Server
|
stunServer *stun.Server
|
||||||
@@ -328,24 +324,19 @@ func setupServerHooks(servers *serverInstances, cfg *CombinedConfig) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, ok := servers.mgmtSrv.GetContainer(mgmtServer.ContainerKeyBaseServer); ok {
|
servers.mgmtSrv.AfterInit(func(s *mgmtServer.BaseServer) {
|
||||||
if baseServer, ok := s.(*mgmtServer.BaseServer); ok {
|
grpcSrv := s.GRPCServer()
|
||||||
baseServer.AfterInit(func(s *mgmtServer.BaseServer) {
|
|
||||||
grpcSrv := s.GRPCServer()
|
|
||||||
|
|
||||||
if servers.signalSrv != nil {
|
if servers.signalSrv != nil {
|
||||||
proto.RegisterSignalExchangeServer(grpcSrv, servers.signalSrv)
|
proto.RegisterSignalExchangeServer(grpcSrv, servers.signalSrv)
|
||||||
log.Infof("Signal server registered on port %s", cfg.Server.ListenAddress)
|
log.Infof("Signal server registered on port %s", cfg.Server.ListenAddress)
|
||||||
}
|
|
||||||
|
|
||||||
s.SetHandlerFunc(createCombinedHandler(grpcSrv, s.APIHandler(), s.IDPHandler(), servers.relaySrv, servers.metricsServer.Meter, cfg))
|
|
||||||
if servers.relaySrv != nil {
|
|
||||||
log.Infof("Relay WebSocket handler added (path: /relay)")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
s.SetHandlerFunc(createCombinedHandler(grpcSrv, s.APIHandler(), servers.relaySrv, servers.metricsServer.Meter, cfg))
|
||||||
|
if servers.relaySrv != nil {
|
||||||
|
log.Infof("Relay WebSocket handler added (path: /relay)")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func startServers(wg *sync.WaitGroup, srv *relayServer.Server, httpHealthcheck *healthcheck.Server, stunServer *stun.Server, metricsServer *sharedMetrics.Metrics) {
|
func startServers(wg *sync.WaitGroup, srv *relayServer.Server, httpHealthcheck *healthcheck.Server, stunServer *stun.Server, metricsServer *sharedMetrics.Metrics) {
|
||||||
@@ -355,32 +346,38 @@ func startServers(wg *sync.WaitGroup, srv *relayServer.Server, httpHealthcheck *
|
|||||||
log.Infof("Relay WebSocket multiplexed on management port (no separate relay listener)")
|
log.Infof("Relay WebSocket multiplexed on management port (no separate relay listener)")
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Go(func() {
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
log.Infof("running metrics server: %s%s", metricsServer.Addr, metricsServer.Endpoint)
|
log.Infof("running metrics server: %s%s", metricsServer.Addr, metricsServer.Endpoint)
|
||||||
if err := metricsServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
if err := metricsServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Fatalf("failed to start metrics server: %v", err)
|
log.Fatalf("failed to start metrics server: %v", err)
|
||||||
}
|
}
|
||||||
})
|
}()
|
||||||
|
|
||||||
wg.Go(func() {
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
if err := httpHealthcheck.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
if err := httpHealthcheck.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Fatalf("failed to start healthcheck server: %v", err)
|
log.Fatalf("failed to start healthcheck server: %v", err)
|
||||||
}
|
}
|
||||||
})
|
}()
|
||||||
|
|
||||||
if stunServer != nil {
|
if stunServer != nil {
|
||||||
wg.Go(func() {
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
if err := stunServer.Listen(); err != nil {
|
if err := stunServer.Listen(); err != nil {
|
||||||
if errors.Is(err, stun.ErrServerClosed) {
|
if errors.Is(err, stun.ErrServerClosed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Errorf("STUN server error: %v", err)
|
log.Errorf("STUN server error: %v", err)
|
||||||
}
|
}
|
||||||
})
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func shutdownServers(ctx context.Context, srv *relayServer.Server, httpHealthcheck *healthcheck.Server, stunServer *stun.Server, mgmtSrv mgmtServer.Server, metricsServer *sharedMetrics.Metrics) error {
|
func shutdownServers(ctx context.Context, srv *relayServer.Server, httpHealthcheck *healthcheck.Server, stunServer *stun.Server, mgmtSrv *mgmtServer.BaseServer, metricsServer *sharedMetrics.Metrics) error {
|
||||||
var errs error
|
var errs error
|
||||||
|
|
||||||
if err := httpHealthcheck.Shutdown(ctx); err != nil {
|
if err := httpHealthcheck.Shutdown(ctx); err != nil {
|
||||||
@@ -494,7 +491,7 @@ func handleTLSConfig(cfg *CombinedConfig) (*tls.Config, bool, error) {
|
|||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createManagementServer(cfg *CombinedConfig, mgmtConfig *nbconfig.Config) (mgmtServer.Server, error) {
|
func createManagementServer(cfg *CombinedConfig, mgmtConfig *nbconfig.Config) (*mgmtServer.BaseServer, error) {
|
||||||
mgmt := cfg.Management
|
mgmt := cfg.Management
|
||||||
|
|
||||||
// Extract port from listen address
|
// Extract port from listen address
|
||||||
@@ -505,7 +502,7 @@ func createManagementServer(cfg *CombinedConfig, mgmtConfig *nbconfig.Config) (m
|
|||||||
}
|
}
|
||||||
mgmtPort, _ := strconv.Atoi(portStr)
|
mgmtPort, _ := strconv.Atoi(portStr)
|
||||||
|
|
||||||
mgmtSrv := newServer(
|
mgmtSrv := mgmtServer.NewServer(
|
||||||
&mgmtServer.Config{
|
&mgmtServer.Config{
|
||||||
NbConfig: mgmtConfig,
|
NbConfig: mgmtConfig,
|
||||||
DNSDomain: "",
|
DNSDomain: "",
|
||||||
@@ -524,7 +521,7 @@ func createManagementServer(cfg *CombinedConfig, mgmtConfig *nbconfig.Config) (m
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createCombinedHandler creates an HTTP handler that multiplexes Management, Signal (via wsproxy), and Relay WebSocket traffic
|
// createCombinedHandler creates an HTTP handler that multiplexes Management, Signal (via wsproxy), and Relay WebSocket traffic
|
||||||
func createCombinedHandler(grpcServer *grpc.Server, httpHandler http.Handler, idpHandler http.Handler, relaySrv *relayServer.Server, meter metric.Meter, cfg *CombinedConfig) http.Handler {
|
func createCombinedHandler(grpcServer *grpc.Server, httpHandler http.Handler, relaySrv *relayServer.Server, meter metric.Meter, cfg *CombinedConfig) http.Handler {
|
||||||
wsProxy := wsproxyserver.New(grpcServer, wsproxyserver.WithOTelMeter(meter))
|
wsProxy := wsproxyserver.New(grpcServer, wsproxyserver.WithOTelMeter(meter))
|
||||||
|
|
||||||
var relayAcceptFn func(conn listener.Conn)
|
var relayAcceptFn func(conn listener.Conn)
|
||||||
@@ -559,10 +556,6 @@ func createCombinedHandler(grpcServer *grpc.Server, httpHandler http.Handler, id
|
|||||||
http.Error(w, "Relay service not enabled", http.StatusNotFound)
|
http.Error(w, "Relay service not enabled", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Embedded IdP (Dex)
|
|
||||||
case idpHandler != nil && strings.HasPrefix(r.URL.Path, "/oauth2"):
|
|
||||||
idpHandler.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
// Management HTTP API (default)
|
// Management HTTP API (default)
|
||||||
default:
|
default:
|
||||||
httpHandler.ServeHTTP(w, r)
|
httpHandler.ServeHTTP(w, r)
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
mgmtServer "github.com/netbirdio/netbird/management/internals/server"
|
|
||||||
)
|
|
||||||
|
|
||||||
var newServer = func(cfg *mgmtServer.Config) mgmtServer.Server {
|
|
||||||
return mgmtServer.NewServer(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetNewServer(fn func(*mgmtServer.Config) mgmtServer.Server) {
|
|
||||||
newServer = fn
|
|
||||||
}
|
|
||||||
@@ -1,2 +1,28 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if ! which realpath > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo realpath is not installed
|
||||||
|
echo run: brew install coreutils
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
old_pwd=$(pwd)
|
||||||
|
script_path=$(dirname "$(realpath "$0")")
|
||||||
|
cd "$script_path/.."
|
||||||
|
|
||||||
|
repo_root=$(git rev-parse --show-toplevel)
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$repo_root/proto-tools.env"
|
||||||
|
|
||||||
|
actual_protoc=$(protoc --version | awk '{print $2}')
|
||||||
|
if [[ "$actual_protoc" != "$PROTOC_VERSION" ]]; then
|
||||||
|
echo "ERROR: protoc version $actual_protoc differs from pinned $PROTOC_VERSION" >&2
|
||||||
|
echo "Install protoc $PROTOC_VERSION from https://github.com/protocolbuffers/protobuf/releases" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
go install "google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VERSION}"
|
||||||
protoc -I testprotos/ testprotos/testproto.proto --go_out=.
|
protoc -I testprotos/ testprotos/testproto.proto --go_out=.
|
||||||
|
cd "$old_pwd"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.36.6
|
||||||
// protoc v3.12.4
|
// protoc v6.33.1
|
||||||
// source: testproto.proto
|
// source: testproto.proto
|
||||||
|
|
||||||
package testprotos
|
package testprotos
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -21,20 +22,17 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TestMessage struct {
|
type TestMessage struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Body string `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Body string `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *TestMessage) Reset() {
|
func (x *TestMessage) Reset() {
|
||||||
*x = TestMessage{}
|
*x = TestMessage{}
|
||||||
if protoimpl.UnsafeEnabled {
|
mi := &file_testproto_proto_msgTypes[0]
|
||||||
mi := &file_testproto_proto_msgTypes[0]
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms.StoreMessageInfo(mi)
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *TestMessage) String() string {
|
func (x *TestMessage) String() string {
|
||||||
@@ -45,7 +43,7 @@ func (*TestMessage) ProtoMessage() {}
|
|||||||
|
|
||||||
func (x *TestMessage) ProtoReflect() protoreflect.Message {
|
func (x *TestMessage) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_testproto_proto_msgTypes[0]
|
mi := &file_testproto_proto_msgTypes[0]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
@@ -69,29 +67,27 @@ func (x *TestMessage) GetBody() string {
|
|||||||
|
|
||||||
var File_testproto_proto protoreflect.FileDescriptor
|
var File_testproto_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_testproto_proto_rawDesc = []byte{
|
const file_testproto_proto_rawDesc = "" +
|
||||||
0x0a, 0x0f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
"\n" +
|
||||||
0x6f, 0x12, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x21, 0x0a,
|
"\x0ftestproto.proto\x12\n" +
|
||||||
0x0b, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04,
|
"testprotos\"!\n" +
|
||||||
0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79,
|
"\vTestMessage\x12\x12\n" +
|
||||||
0x42, 0x0d, 0x5a, 0x0b, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62,
|
"\x04body\x18\x01 \x01(\tR\x04bodyB\rZ\v/testprotosb\x06proto3"
|
||||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_testproto_proto_rawDescOnce sync.Once
|
file_testproto_proto_rawDescOnce sync.Once
|
||||||
file_testproto_proto_rawDescData = file_testproto_proto_rawDesc
|
file_testproto_proto_rawDescData []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
func file_testproto_proto_rawDescGZIP() []byte {
|
func file_testproto_proto_rawDescGZIP() []byte {
|
||||||
file_testproto_proto_rawDescOnce.Do(func() {
|
file_testproto_proto_rawDescOnce.Do(func() {
|
||||||
file_testproto_proto_rawDescData = protoimpl.X.CompressGZIP(file_testproto_proto_rawDescData)
|
file_testproto_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_testproto_proto_rawDesc), len(file_testproto_proto_rawDesc)))
|
||||||
})
|
})
|
||||||
return file_testproto_proto_rawDescData
|
return file_testproto_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_testproto_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
var file_testproto_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
var file_testproto_proto_goTypes = []interface{}{
|
var file_testproto_proto_goTypes = []any{
|
||||||
(*TestMessage)(nil), // 0: testprotos.TestMessage
|
(*TestMessage)(nil), // 0: testprotos.TestMessage
|
||||||
}
|
}
|
||||||
var file_testproto_proto_depIdxs = []int32{
|
var file_testproto_proto_depIdxs = []int32{
|
||||||
@@ -107,25 +103,11 @@ func file_testproto_proto_init() {
|
|||||||
if File_testproto_proto != nil {
|
if File_testproto_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !protoimpl.UnsafeEnabled {
|
|
||||||
file_testproto_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*TestMessage); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_testproto_proto_rawDesc,
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_testproto_proto_rawDesc), len(file_testproto_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 1,
|
NumMessages: 1,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
@@ -136,7 +118,6 @@ func file_testproto_proto_init() {
|
|||||||
MessageInfos: file_testproto_proto_msgTypes,
|
MessageInfos: file_testproto_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_testproto_proto = out.File
|
File_testproto_proto = out.File
|
||||||
file_testproto_proto_rawDesc = nil
|
|
||||||
file_testproto_proto_goTypes = nil
|
file_testproto_proto_goTypes = nil
|
||||||
file_testproto_proto_depIdxs = nil
|
file_testproto_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.36.6
|
||||||
// protoc v3.21.9
|
// protoc v6.33.1
|
||||||
// source: flow.proto
|
// source: flow.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -125,27 +126,24 @@ func (Direction) EnumDescriptor() ([]byte, []int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FlowEvent struct {
|
type FlowEvent struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
// Unique client event identifier
|
// Unique client event identifier
|
||||||
EventId []byte `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
|
EventId []byte `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
|
||||||
// When the event occurred
|
// When the event occurred
|
||||||
Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||||
// Public key of the sending peer
|
// Public key of the sending peer
|
||||||
PublicKey []byte `protobuf:"bytes,3,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
|
PublicKey []byte `protobuf:"bytes,3,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
|
||||||
FlowFields *FlowFields `protobuf:"bytes,4,opt,name=flow_fields,json=flowFields,proto3" json:"flow_fields,omitempty"`
|
FlowFields *FlowFields `protobuf:"bytes,4,opt,name=flow_fields,json=flowFields,proto3" json:"flow_fields,omitempty"`
|
||||||
IsInitiator bool `protobuf:"varint,5,opt,name=isInitiator,proto3" json:"isInitiator,omitempty"`
|
IsInitiator bool `protobuf:"varint,5,opt,name=isInitiator,proto3" json:"isInitiator,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FlowEvent) Reset() {
|
func (x *FlowEvent) Reset() {
|
||||||
*x = FlowEvent{}
|
*x = FlowEvent{}
|
||||||
if protoimpl.UnsafeEnabled {
|
mi := &file_flow_proto_msgTypes[0]
|
||||||
mi := &file_flow_proto_msgTypes[0]
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms.StoreMessageInfo(mi)
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FlowEvent) String() string {
|
func (x *FlowEvent) String() string {
|
||||||
@@ -156,7 +154,7 @@ func (*FlowEvent) ProtoMessage() {}
|
|||||||
|
|
||||||
func (x *FlowEvent) ProtoReflect() protoreflect.Message {
|
func (x *FlowEvent) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_flow_proto_msgTypes[0]
|
mi := &file_flow_proto_msgTypes[0]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
@@ -207,22 +205,19 @@ func (x *FlowEvent) GetIsInitiator() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FlowEventAck struct {
|
type FlowEventAck struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
// Unique client event identifier that has been ack'ed
|
// Unique client event identifier that has been ack'ed
|
||||||
EventId []byte `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
|
EventId []byte `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
|
||||||
IsInitiator bool `protobuf:"varint,2,opt,name=isInitiator,proto3" json:"isInitiator,omitempty"`
|
IsInitiator bool `protobuf:"varint,2,opt,name=isInitiator,proto3" json:"isInitiator,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FlowEventAck) Reset() {
|
func (x *FlowEventAck) Reset() {
|
||||||
*x = FlowEventAck{}
|
*x = FlowEventAck{}
|
||||||
if protoimpl.UnsafeEnabled {
|
mi := &file_flow_proto_msgTypes[1]
|
||||||
mi := &file_flow_proto_msgTypes[1]
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms.StoreMessageInfo(mi)
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FlowEventAck) String() string {
|
func (x *FlowEventAck) String() string {
|
||||||
@@ -233,7 +228,7 @@ func (*FlowEventAck) ProtoMessage() {}
|
|||||||
|
|
||||||
func (x *FlowEventAck) ProtoReflect() protoreflect.Message {
|
func (x *FlowEventAck) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_flow_proto_msgTypes[1]
|
mi := &file_flow_proto_msgTypes[1]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
@@ -263,10 +258,7 @@ func (x *FlowEventAck) GetIsInitiator() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FlowFields struct {
|
type FlowFields struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
// Unique client flow session identifier
|
// Unique client flow session identifier
|
||||||
FlowId []byte `protobuf:"bytes,1,opt,name=flow_id,json=flowId,proto3" json:"flow_id,omitempty"`
|
FlowId []byte `protobuf:"bytes,1,opt,name=flow_id,json=flowId,proto3" json:"flow_id,omitempty"`
|
||||||
// Flow type
|
// Flow type
|
||||||
@@ -283,7 +275,7 @@ type FlowFields struct {
|
|||||||
DestIp []byte `protobuf:"bytes,7,opt,name=dest_ip,json=destIp,proto3" json:"dest_ip,omitempty"`
|
DestIp []byte `protobuf:"bytes,7,opt,name=dest_ip,json=destIp,proto3" json:"dest_ip,omitempty"`
|
||||||
// Layer 4 -specific information
|
// Layer 4 -specific information
|
||||||
//
|
//
|
||||||
// Types that are assignable to ConnectionInfo:
|
// Types that are valid to be assigned to ConnectionInfo:
|
||||||
//
|
//
|
||||||
// *FlowFields_PortInfo
|
// *FlowFields_PortInfo
|
||||||
// *FlowFields_IcmpInfo
|
// *FlowFields_IcmpInfo
|
||||||
@@ -297,15 +289,15 @@ type FlowFields struct {
|
|||||||
// Resource ID
|
// Resource ID
|
||||||
SourceResourceId []byte `protobuf:"bytes,14,opt,name=source_resource_id,json=sourceResourceId,proto3" json:"source_resource_id,omitempty"`
|
SourceResourceId []byte `protobuf:"bytes,14,opt,name=source_resource_id,json=sourceResourceId,proto3" json:"source_resource_id,omitempty"`
|
||||||
DestResourceId []byte `protobuf:"bytes,15,opt,name=dest_resource_id,json=destResourceId,proto3" json:"dest_resource_id,omitempty"`
|
DestResourceId []byte `protobuf:"bytes,15,opt,name=dest_resource_id,json=destResourceId,proto3" json:"dest_resource_id,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FlowFields) Reset() {
|
func (x *FlowFields) Reset() {
|
||||||
*x = FlowFields{}
|
*x = FlowFields{}
|
||||||
if protoimpl.UnsafeEnabled {
|
mi := &file_flow_proto_msgTypes[2]
|
||||||
mi := &file_flow_proto_msgTypes[2]
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms.StoreMessageInfo(mi)
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FlowFields) String() string {
|
func (x *FlowFields) String() string {
|
||||||
@@ -316,7 +308,7 @@ func (*FlowFields) ProtoMessage() {}
|
|||||||
|
|
||||||
func (x *FlowFields) ProtoReflect() protoreflect.Message {
|
func (x *FlowFields) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_flow_proto_msgTypes[2]
|
mi := &file_flow_proto_msgTypes[2]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
@@ -380,23 +372,27 @@ func (x *FlowFields) GetDestIp() []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *FlowFields) GetConnectionInfo() isFlowFields_ConnectionInfo {
|
func (x *FlowFields) GetConnectionInfo() isFlowFields_ConnectionInfo {
|
||||||
if m != nil {
|
if x != nil {
|
||||||
return m.ConnectionInfo
|
return x.ConnectionInfo
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FlowFields) GetPortInfo() *PortInfo {
|
func (x *FlowFields) GetPortInfo() *PortInfo {
|
||||||
if x, ok := x.GetConnectionInfo().(*FlowFields_PortInfo); ok {
|
if x != nil {
|
||||||
return x.PortInfo
|
if x, ok := x.ConnectionInfo.(*FlowFields_PortInfo); ok {
|
||||||
|
return x.PortInfo
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FlowFields) GetIcmpInfo() *ICMPInfo {
|
func (x *FlowFields) GetIcmpInfo() *ICMPInfo {
|
||||||
if x, ok := x.GetConnectionInfo().(*FlowFields_IcmpInfo); ok {
|
if x != nil {
|
||||||
return x.IcmpInfo
|
if x, ok := x.ConnectionInfo.(*FlowFields_IcmpInfo); ok {
|
||||||
|
return x.IcmpInfo
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -463,21 +459,18 @@ func (*FlowFields_IcmpInfo) isFlowFields_ConnectionInfo() {}
|
|||||||
|
|
||||||
// TCP/UDP port information
|
// TCP/UDP port information
|
||||||
type PortInfo struct {
|
type PortInfo struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
SourcePort uint32 `protobuf:"varint,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"`
|
||||||
|
DestPort uint32 `protobuf:"varint,2,opt,name=dest_port,json=destPort,proto3" json:"dest_port,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
SourcePort uint32 `protobuf:"varint,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"`
|
|
||||||
DestPort uint32 `protobuf:"varint,2,opt,name=dest_port,json=destPort,proto3" json:"dest_port,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PortInfo) Reset() {
|
func (x *PortInfo) Reset() {
|
||||||
*x = PortInfo{}
|
*x = PortInfo{}
|
||||||
if protoimpl.UnsafeEnabled {
|
mi := &file_flow_proto_msgTypes[3]
|
||||||
mi := &file_flow_proto_msgTypes[3]
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms.StoreMessageInfo(mi)
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PortInfo) String() string {
|
func (x *PortInfo) String() string {
|
||||||
@@ -488,7 +481,7 @@ func (*PortInfo) ProtoMessage() {}
|
|||||||
|
|
||||||
func (x *PortInfo) ProtoReflect() protoreflect.Message {
|
func (x *PortInfo) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_flow_proto_msgTypes[3]
|
mi := &file_flow_proto_msgTypes[3]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
@@ -519,21 +512,18 @@ func (x *PortInfo) GetDestPort() uint32 {
|
|||||||
|
|
||||||
// ICMP message information
|
// ICMP message information
|
||||||
type ICMPInfo struct {
|
type ICMPInfo struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
IcmpType uint32 `protobuf:"varint,1,opt,name=icmp_type,json=icmpType,proto3" json:"icmp_type,omitempty"`
|
||||||
|
IcmpCode uint32 `protobuf:"varint,2,opt,name=icmp_code,json=icmpCode,proto3" json:"icmp_code,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
IcmpType uint32 `protobuf:"varint,1,opt,name=icmp_type,json=icmpType,proto3" json:"icmp_type,omitempty"`
|
|
||||||
IcmpCode uint32 `protobuf:"varint,2,opt,name=icmp_code,json=icmpCode,proto3" json:"icmp_code,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ICMPInfo) Reset() {
|
func (x *ICMPInfo) Reset() {
|
||||||
*x = ICMPInfo{}
|
*x = ICMPInfo{}
|
||||||
if protoimpl.UnsafeEnabled {
|
mi := &file_flow_proto_msgTypes[4]
|
||||||
mi := &file_flow_proto_msgTypes[4]
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms.StoreMessageInfo(mi)
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ICMPInfo) String() string {
|
func (x *ICMPInfo) String() string {
|
||||||
@@ -544,7 +534,7 @@ func (*ICMPInfo) ProtoMessage() {}
|
|||||||
|
|
||||||
func (x *ICMPInfo) ProtoReflect() protoreflect.Message {
|
func (x *ICMPInfo) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_flow_proto_msgTypes[4]
|
mi := &file_flow_proto_msgTypes[4]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
@@ -575,102 +565,79 @@ func (x *ICMPInfo) GetIcmpCode() uint32 {
|
|||||||
|
|
||||||
var File_flow_proto protoreflect.FileDescriptor
|
var File_flow_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_flow_proto_rawDesc = []byte{
|
const file_flow_proto_rawDesc = "" +
|
||||||
0x0a, 0x0a, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x66, 0x6c,
|
"\n" +
|
||||||
0x6f, 0x77, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
"\n" +
|
||||||
0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72,
|
"flow.proto\x12\x04flow\x1a\x1fgoogle/protobuf/timestamp.proto\"\xd4\x01\n" +
|
||||||
0x6f, 0x74, 0x6f, 0x22, 0xd4, 0x01, 0x0a, 0x09, 0x46, 0x6c, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e,
|
"\tFlowEvent\x12\x19\n" +
|
||||||
0x74, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
|
"\bevent_id\x18\x01 \x01(\fR\aeventId\x128\n" +
|
||||||
0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09,
|
"\ttimestamp\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12\x1d\n" +
|
||||||
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
"\n" +
|
||||||
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
"public_key\x18\x03 \x01(\fR\tpublicKey\x121\n" +
|
||||||
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d,
|
"\vflow_fields\x18\x04 \x01(\v2\x10.flow.FlowFieldsR\n" +
|
||||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
|
"flowFields\x12 \n" +
|
||||||
0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c,
|
"\visInitiator\x18\x05 \x01(\bR\visInitiator\"K\n" +
|
||||||
0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x66, 0x69,
|
"\fFlowEventAck\x12\x19\n" +
|
||||||
0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x66, 0x6c, 0x6f,
|
"\bevent_id\x18\x01 \x01(\fR\aeventId\x12 \n" +
|
||||||
0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x0a, 0x66, 0x6c,
|
"\visInitiator\x18\x02 \x01(\bR\visInitiator\"\x9c\x04\n" +
|
||||||
0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x69, 0x73, 0x49, 0x6e,
|
"\n" +
|
||||||
0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69,
|
"FlowFields\x12\x17\n" +
|
||||||
0x73, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x4b, 0x0a, 0x0c, 0x46, 0x6c,
|
"\aflow_id\x18\x01 \x01(\fR\x06flowId\x12\x1e\n" +
|
||||||
0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x41, 0x63, 0x6b, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76,
|
"\x04type\x18\x02 \x01(\x0e2\n" +
|
||||||
0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x76,
|
".flow.TypeR\x04type\x12\x17\n" +
|
||||||
0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x69, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x69,
|
"\arule_id\x18\x03 \x01(\fR\x06ruleId\x12-\n" +
|
||||||
0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x49, 0x6e,
|
"\tdirection\x18\x04 \x01(\x0e2\x0f.flow.DirectionR\tdirection\x12\x1a\n" +
|
||||||
0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x9c, 0x04, 0x0a, 0x0a, 0x46, 0x6c, 0x6f, 0x77,
|
"\bprotocol\x18\x05 \x01(\rR\bprotocol\x12\x1b\n" +
|
||||||
0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69,
|
"\tsource_ip\x18\x06 \x01(\fR\bsourceIp\x12\x17\n" +
|
||||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x12,
|
"\adest_ip\x18\a \x01(\fR\x06destIp\x12-\n" +
|
||||||
0x1e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0a, 0x2e,
|
"\tport_info\x18\b \x01(\v2\x0e.flow.PortInfoH\x00R\bportInfo\x12-\n" +
|
||||||
0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12,
|
"\ticmp_info\x18\t \x01(\v2\x0e.flow.ICMPInfoH\x00R\bicmpInfo\x12\x1d\n" +
|
||||||
0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c,
|
"\n" +
|
||||||
0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65,
|
"rx_packets\x18\n" +
|
||||||
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x66, 0x6c,
|
" \x01(\x04R\trxPackets\x12\x1d\n" +
|
||||||
0x6f, 0x77, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x64, 0x69,
|
"\n" +
|
||||||
0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
"tx_packets\x18\v \x01(\x04R\ttxPackets\x12\x19\n" +
|
||||||
0x63, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
"\brx_bytes\x18\f \x01(\x04R\arxBytes\x12\x19\n" +
|
||||||
0x63, 0x6f, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x70,
|
"\btx_bytes\x18\r \x01(\x04R\atxBytes\x12,\n" +
|
||||||
0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x70,
|
"\x12source_resource_id\x18\x0e \x01(\fR\x10sourceResourceId\x12(\n" +
|
||||||
0x12, 0x17, 0x0a, 0x07, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28,
|
"\x10dest_resource_id\x18\x0f \x01(\fR\x0edestResourceIdB\x11\n" +
|
||||||
0x0c, 0x52, 0x06, 0x64, 0x65, 0x73, 0x74, 0x49, 0x70, 0x12, 0x2d, 0x0a, 0x09, 0x70, 0x6f, 0x72,
|
"\x0fconnection_info\"H\n" +
|
||||||
0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x66,
|
"\bPortInfo\x12\x1f\n" +
|
||||||
0x6c, 0x6f, 0x77, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x08,
|
"\vsource_port\x18\x01 \x01(\rR\n" +
|
||||||
0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2d, 0x0a, 0x09, 0x69, 0x63, 0x6d, 0x70,
|
"sourcePort\x12\x1b\n" +
|
||||||
0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x66, 0x6c,
|
"\tdest_port\x18\x02 \x01(\rR\bdestPort\"D\n" +
|
||||||
0x6f, 0x77, 0x2e, 0x49, 0x43, 0x4d, 0x50, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x08, 0x69,
|
"\bICMPInfo\x12\x1b\n" +
|
||||||
0x63, 0x6d, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x78, 0x5f, 0x70, 0x61,
|
"\ticmp_type\x18\x01 \x01(\rR\bicmpType\x12\x1b\n" +
|
||||||
0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x78, 0x50,
|
"\ticmp_code\x18\x02 \x01(\rR\bicmpCode*E\n" +
|
||||||
0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63,
|
"\x04Type\x12\x10\n" +
|
||||||
0x6b, 0x65, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x78, 0x50, 0x61,
|
"\fTYPE_UNKNOWN\x10\x00\x12\x0e\n" +
|
||||||
0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65,
|
"\n" +
|
||||||
0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x72, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73,
|
"TYPE_START\x10\x01\x12\f\n" +
|
||||||
0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x01,
|
"\bTYPE_END\x10\x02\x12\r\n" +
|
||||||
0x28, 0x04, 0x52, 0x07, 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x73,
|
"\tTYPE_DROP\x10\x03*;\n" +
|
||||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69,
|
"\tDirection\x12\x15\n" +
|
||||||
0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52,
|
"\x11DIRECTION_UNKNOWN\x10\x00\x12\v\n" +
|
||||||
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x65, 0x73,
|
"\aINGRESS\x10\x01\x12\n" +
|
||||||
0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20,
|
"\n" +
|
||||||
0x01, 0x28, 0x0c, 0x52, 0x0e, 0x64, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
|
"\x06EGRESS\x10\x022B\n" +
|
||||||
0x65, 0x49, 0x64, 0x42, 0x11, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f,
|
"\vFlowService\x123\n" +
|
||||||
0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x48, 0x0a, 0x08, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e,
|
"\x06Events\x12\x0f.flow.FlowEvent\x1a\x12.flow.FlowEventAck\"\x00(\x010\x01B\bZ\x06/protob\x06proto3"
|
||||||
0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x72,
|
|
||||||
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50,
|
|
||||||
0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74,
|
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x64, 0x65, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74,
|
|
||||||
0x22, 0x44, 0x0a, 0x08, 0x49, 0x43, 0x4d, 0x50, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x09,
|
|
||||||
0x69, 0x63, 0x6d, 0x70, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
|
||||||
0x08, 0x69, 0x63, 0x6d, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x63, 0x6d,
|
|
||||||
0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x69, 0x63,
|
|
||||||
0x6d, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x2a, 0x45, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10,
|
|
||||||
0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,
|
|
||||||
0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x01,
|
|
||||||
0x12, 0x0c, 0x0a, 0x08, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x0d,
|
|
||||||
0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x03, 0x2a, 0x3b, 0x0a,
|
|
||||||
0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x49,
|
|
||||||
0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
|
|
||||||
0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x0a,
|
|
||||||
0x0a, 0x06, 0x45, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x02, 0x32, 0x42, 0x0a, 0x0b, 0x46, 0x6c,
|
|
||||||
0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x45, 0x76, 0x65,
|
|
||||||
0x6e, 0x74, 0x73, 0x12, 0x0f, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x45,
|
|
||||||
0x76, 0x65, 0x6e, 0x74, 0x1a, 0x12, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77,
|
|
||||||
0x45, 0x76, 0x65, 0x6e, 0x74, 0x41, 0x63, 0x6b, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08,
|
|
||||||
0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_flow_proto_rawDescOnce sync.Once
|
file_flow_proto_rawDescOnce sync.Once
|
||||||
file_flow_proto_rawDescData = file_flow_proto_rawDesc
|
file_flow_proto_rawDescData []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
func file_flow_proto_rawDescGZIP() []byte {
|
func file_flow_proto_rawDescGZIP() []byte {
|
||||||
file_flow_proto_rawDescOnce.Do(func() {
|
file_flow_proto_rawDescOnce.Do(func() {
|
||||||
file_flow_proto_rawDescData = protoimpl.X.CompressGZIP(file_flow_proto_rawDescData)
|
file_flow_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_flow_proto_rawDesc), len(file_flow_proto_rawDesc)))
|
||||||
})
|
})
|
||||||
return file_flow_proto_rawDescData
|
return file_flow_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_flow_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
var file_flow_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||||
var file_flow_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
var file_flow_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||||
var file_flow_proto_goTypes = []interface{}{
|
var file_flow_proto_goTypes = []any{
|
||||||
(Type)(0), // 0: flow.Type
|
(Type)(0), // 0: flow.Type
|
||||||
(Direction)(0), // 1: flow.Direction
|
(Direction)(0), // 1: flow.Direction
|
||||||
(*FlowEvent)(nil), // 2: flow.FlowEvent
|
(*FlowEvent)(nil), // 2: flow.FlowEvent
|
||||||
@@ -701,69 +668,7 @@ func file_flow_proto_init() {
|
|||||||
if File_flow_proto != nil {
|
if File_flow_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !protoimpl.UnsafeEnabled {
|
file_flow_proto_msgTypes[2].OneofWrappers = []any{
|
||||||
file_flow_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*FlowEvent); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_flow_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*FlowEventAck); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_flow_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*FlowFields); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_flow_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*PortInfo); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_flow_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*ICMPInfo); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_flow_proto_msgTypes[2].OneofWrappers = []interface{}{
|
|
||||||
(*FlowFields_PortInfo)(nil),
|
(*FlowFields_PortInfo)(nil),
|
||||||
(*FlowFields_IcmpInfo)(nil),
|
(*FlowFields_IcmpInfo)(nil),
|
||||||
}
|
}
|
||||||
@@ -771,7 +676,7 @@ func file_flow_proto_init() {
|
|||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_flow_proto_rawDesc,
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_flow_proto_rawDesc), len(file_flow_proto_rawDesc)),
|
||||||
NumEnums: 2,
|
NumEnums: 2,
|
||||||
NumMessages: 5,
|
NumMessages: 5,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
@@ -783,7 +688,6 @@ func file_flow_proto_init() {
|
|||||||
MessageInfos: file_flow_proto_msgTypes,
|
MessageInfos: file_flow_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_flow_proto = out.File
|
File_flow_proto = out.File
|
||||||
file_flow_proto_rawDesc = nil
|
|
||||||
file_flow_proto_goTypes = nil
|
file_flow_proto_goTypes = nil
|
||||||
file_flow_proto_depIdxs = nil
|
file_flow_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.6.1
|
||||||
|
// - protoc v6.33.1
|
||||||
|
// source: flow.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
|
|
||||||
@@ -11,15 +15,19 @@ import (
|
|||||||
|
|
||||||
// This is a compile-time assertion to ensure that this generated file
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
// is compatible with the grpc package it is being compiled against.
|
// is compatible with the grpc package it is being compiled against.
|
||||||
// Requires gRPC-Go v1.32.0 or later.
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
const _ = grpc.SupportPackageIsVersion7
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
|
const (
|
||||||
|
FlowService_Events_FullMethodName = "/flow.FlowService/Events"
|
||||||
|
)
|
||||||
|
|
||||||
// FlowServiceClient is the client API for FlowService service.
|
// FlowServiceClient is the client API for FlowService service.
|
||||||
//
|
//
|
||||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
type FlowServiceClient interface {
|
type FlowServiceClient interface {
|
||||||
// Client to receiver streams of events and acknowledgements
|
// Client to receiver streams of events and acknowledgements
|
||||||
Events(ctx context.Context, opts ...grpc.CallOption) (FlowService_EventsClient, error)
|
Events(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[FlowEvent, FlowEventAck], error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type flowServiceClient struct {
|
type flowServiceClient struct {
|
||||||
@@ -30,54 +38,40 @@ func NewFlowServiceClient(cc grpc.ClientConnInterface) FlowServiceClient {
|
|||||||
return &flowServiceClient{cc}
|
return &flowServiceClient{cc}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *flowServiceClient) Events(ctx context.Context, opts ...grpc.CallOption) (FlowService_EventsClient, error) {
|
func (c *flowServiceClient) Events(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[FlowEvent, FlowEventAck], error) {
|
||||||
stream, err := c.cc.NewStream(ctx, &FlowService_ServiceDesc.Streams[0], "/flow.FlowService/Events", opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
stream, err := c.cc.NewStream(ctx, &FlowService_ServiceDesc.Streams[0], FlowService_Events_FullMethodName, cOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
x := &flowServiceEventsClient{stream}
|
x := &grpc.GenericClientStream[FlowEvent, FlowEventAck]{ClientStream: stream}
|
||||||
return x, nil
|
return x, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlowService_EventsClient interface {
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
Send(*FlowEvent) error
|
type FlowService_EventsClient = grpc.BidiStreamingClient[FlowEvent, FlowEventAck]
|
||||||
Recv() (*FlowEventAck, error)
|
|
||||||
grpc.ClientStream
|
|
||||||
}
|
|
||||||
|
|
||||||
type flowServiceEventsClient struct {
|
|
||||||
grpc.ClientStream
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *flowServiceEventsClient) Send(m *FlowEvent) error {
|
|
||||||
return x.ClientStream.SendMsg(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *flowServiceEventsClient) Recv() (*FlowEventAck, error) {
|
|
||||||
m := new(FlowEventAck)
|
|
||||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlowServiceServer is the server API for FlowService service.
|
// FlowServiceServer is the server API for FlowService service.
|
||||||
// All implementations must embed UnimplementedFlowServiceServer
|
// All implementations must embed UnimplementedFlowServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility.
|
||||||
type FlowServiceServer interface {
|
type FlowServiceServer interface {
|
||||||
// Client to receiver streams of events and acknowledgements
|
// Client to receiver streams of events and acknowledgements
|
||||||
Events(FlowService_EventsServer) error
|
Events(grpc.BidiStreamingServer[FlowEvent, FlowEventAck]) error
|
||||||
mustEmbedUnimplementedFlowServiceServer()
|
mustEmbedUnimplementedFlowServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnimplementedFlowServiceServer must be embedded to have forward compatible implementations.
|
// UnimplementedFlowServiceServer must be embedded to have
|
||||||
type UnimplementedFlowServiceServer struct {
|
// forward compatible implementations.
|
||||||
}
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedFlowServiceServer struct{}
|
||||||
|
|
||||||
func (UnimplementedFlowServiceServer) Events(FlowService_EventsServer) error {
|
func (UnimplementedFlowServiceServer) Events(grpc.BidiStreamingServer[FlowEvent, FlowEventAck]) error {
|
||||||
return status.Errorf(codes.Unimplemented, "method Events not implemented")
|
return status.Error(codes.Unimplemented, "method Events not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedFlowServiceServer) mustEmbedUnimplementedFlowServiceServer() {}
|
func (UnimplementedFlowServiceServer) mustEmbedUnimplementedFlowServiceServer() {}
|
||||||
|
func (UnimplementedFlowServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
// UnsafeFlowServiceServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeFlowServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
// Use of this interface is not recommended, as added methods to FlowServiceServer will
|
// Use of this interface is not recommended, as added methods to FlowServiceServer will
|
||||||
@@ -87,34 +81,22 @@ type UnsafeFlowServiceServer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RegisterFlowServiceServer(s grpc.ServiceRegistrar, srv FlowServiceServer) {
|
func RegisterFlowServiceServer(s grpc.ServiceRegistrar, srv FlowServiceServer) {
|
||||||
|
// If the following call panics, it indicates UnimplementedFlowServiceServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
s.RegisterService(&FlowService_ServiceDesc, srv)
|
s.RegisterService(&FlowService_ServiceDesc, srv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _FlowService_Events_Handler(srv interface{}, stream grpc.ServerStream) error {
|
func _FlowService_Events_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
return srv.(FlowServiceServer).Events(&flowServiceEventsServer{stream})
|
return srv.(FlowServiceServer).Events(&grpc.GenericServerStream[FlowEvent, FlowEventAck]{ServerStream: stream})
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlowService_EventsServer interface {
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
Send(*FlowEventAck) error
|
type FlowService_EventsServer = grpc.BidiStreamingServer[FlowEvent, FlowEventAck]
|
||||||
Recv() (*FlowEvent, error)
|
|
||||||
grpc.ServerStream
|
|
||||||
}
|
|
||||||
|
|
||||||
type flowServiceEventsServer struct {
|
|
||||||
grpc.ServerStream
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *flowServiceEventsServer) Send(m *FlowEventAck) error {
|
|
||||||
return x.ServerStream.SendMsg(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *flowServiceEventsServer) Recv() (*FlowEvent, error) {
|
|
||||||
m := new(FlowEvent)
|
|
||||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlowService_ServiceDesc is the grpc.ServiceDesc for FlowService service.
|
// FlowService_ServiceDesc is the grpc.ServiceDesc for FlowService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
|||||||
@@ -9,9 +9,21 @@ then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
old_pwd=$(pwd)
|
old_pwd=$(pwd)
|
||||||
script_path=$(dirname $(realpath "$0"))
|
script_path=$(dirname "$(realpath "$0")")
|
||||||
cd "$script_path"
|
cd "$script_path"
|
||||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
|
|
||||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
|
repo_root=$(git rev-parse --show-toplevel)
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$repo_root/proto-tools.env"
|
||||||
|
|
||||||
|
actual_protoc=$(protoc --version | awk '{print $2}')
|
||||||
|
if [[ "$actual_protoc" != "$PROTOC_VERSION" ]]; then
|
||||||
|
echo "ERROR: protoc version $actual_protoc differs from pinned $PROTOC_VERSION" >&2
|
||||||
|
echo "Install protoc $PROTOC_VERSION from https://github.com/protocolbuffers/protobuf/releases" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
go install "google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VERSION}"
|
||||||
|
go install "google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION}"
|
||||||
protoc -I ./ ./flow.proto --go_out=../ --go-grpc_out=../
|
protoc -I ./ ./flow.proto --go_out=../ --go-grpc_out=../
|
||||||
cd "$old_pwd"
|
cd "$old_pwd"
|
||||||
|
|||||||
12
go.mod
12
go.mod
@@ -3,7 +3,7 @@ module github.com/netbirdio/netbird
|
|||||||
go 1.25.5
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cunicu.li/go-rosenpass v0.5.42
|
cunicu.li/go-rosenpass v0.4.0
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0
|
github.com/cenkalti/backoff/v4 v4.3.0
|
||||||
github.com/cloudflare/circl v1.3.3 // indirect
|
github.com/cloudflare/circl v1.3.3 // indirect
|
||||||
github.com/golang/protobuf v1.5.4
|
github.com/golang/protobuf v1.5.4
|
||||||
@@ -19,8 +19,8 @@ require (
|
|||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
golang.org/x/crypto v0.50.0
|
golang.org/x/crypto v0.50.0
|
||||||
golang.org/x/sys v0.43.0
|
golang.org/x/sys v0.43.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||||
google.golang.org/grpc v1.80.0
|
google.golang.org/grpc v1.80.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
@@ -38,7 +38,7 @@ require (
|
|||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3
|
||||||
github.com/c-robinson/iplib v1.0.3
|
github.com/c-robinson/iplib v1.0.3
|
||||||
github.com/caddyserver/certmagic v0.21.3
|
github.com/caddyserver/certmagic v0.21.3
|
||||||
github.com/cilium/ebpf v0.19.0
|
github.com/cilium/ebpf v0.15.0
|
||||||
github.com/coder/websocket v1.8.14
|
github.com/coder/websocket v1.8.14
|
||||||
github.com/coreos/go-iptables v0.7.0
|
github.com/coreos/go-iptables v0.7.0
|
||||||
github.com/coreos/go-oidc/v3 v3.18.0
|
github.com/coreos/go-oidc/v3 v3.18.0
|
||||||
@@ -60,7 +60,7 @@ require (
|
|||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
github.com/google/nftables v0.3.0
|
github.com/google/nftables v0.3.0
|
||||||
github.com/gopacket/gopacket v1.4.0
|
github.com/gopacket/gopacket v1.1.1
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357
|
||||||
github.com/hashicorp/go-multierror v1.1.1
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
|
||||||
@@ -335,7 +335,7 @@ replace github.com/kardianos/service => github.com/netbirdio/service v0.0.0-2024
|
|||||||
|
|
||||||
replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949
|
replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949
|
||||||
|
|
||||||
replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20260523085312-4b4a4e36017f
|
replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20260107100953-33b7c9d03db0
|
||||||
|
|
||||||
replace github.com/cloudflare/circl => codeberg.org/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6
|
replace github.com/cloudflare/circl => codeberg.org/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6
|
||||||
|
|
||||||
|
|||||||
26
go.sum
26
go.sum
@@ -7,8 +7,8 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB
|
|||||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||||
codeberg.org/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6 h1:b8xUw3004wk+3ipBhu0VU4RtUJsegMIiqjxSK4++lzA=
|
codeberg.org/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6 h1:b8xUw3004wk+3ipBhu0VU4RtUJsegMIiqjxSK4++lzA=
|
||||||
codeberg.org/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw=
|
codeberg.org/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw=
|
||||||
cunicu.li/go-rosenpass v0.5.42 h1:fRDsGwCxd7DhDgZI1Pxeo8GtNyq8BESZJ7w2/BGGJtU=
|
cunicu.li/go-rosenpass v0.4.0 h1:LtPtBgFWY/9emfgC4glKLEqS0MJTylzV6+ChRhiZERw=
|
||||||
cunicu.li/go-rosenpass v0.5.42/go.mod h1:YRBeyKOe/gWpSX2kpDUec5p9t0XOLsshTguId5gTGVg=
|
cunicu.li/go-rosenpass v0.4.0/go.mod h1:MPbjH9nxV4l3vEagKVdFNwHOketqgS5/To1VYJplf/M=
|
||||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
|
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
|
||||||
@@ -111,8 +111,8 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x
|
|||||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cilium/ebpf v0.19.0 h1:Ro/rE64RmFBeA9FGjcTc+KmCeY6jXmryu6FfnzPRIao=
|
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
||||||
github.com/cilium/ebpf v0.19.0/go.mod h1:fLCgMo3l8tZmAdM3B2XqdFzXBpwkcSTroaVqN08OWVY=
|
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
||||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
@@ -225,8 +225,8 @@ github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3Bum
|
|||||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||||
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s=
|
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||||
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
|
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
@@ -307,8 +307,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA
|
|||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||||
github.com/googleapis/gax-go/v2 v2.21.0 h1:h45NjjzEO3faG9Lg/cFrBh2PgegVVgzqKzuZl/wMbiI=
|
github.com/googleapis/gax-go/v2 v2.21.0 h1:h45NjjzEO3faG9Lg/cFrBh2PgegVVgzqKzuZl/wMbiI=
|
||||||
github.com/googleapis/gax-go/v2 v2.21.0/go.mod h1:But/NJU6TnZsrLai/xBAQLLz+Hc7fHZJt/hsCz3Fih4=
|
github.com/googleapis/gax-go/v2 v2.21.0/go.mod h1:But/NJU6TnZsrLai/xBAQLLz+Hc7fHZJt/hsCz3Fih4=
|
||||||
github.com/gopacket/gopacket v1.4.0 h1:cr1OlFpzksCkZHNO0eLjaSSOrMQnpPXg0j6qHIY3y2U=
|
github.com/gopacket/gopacket v1.1.1 h1:zbx9F9d6A7sWNkFKrvMBZTfGgxFoY4NgUudFVVHMfcw=
|
||||||
github.com/gopacket/gopacket v1.4.0/go.mod h1:EpvsxINeehp5qj4YMKMLf2/dekdhKn2IIAO/ZOifS7o=
|
github.com/gopacket/gopacket v1.1.1/go.mod h1:HavMeONEl7W9036of9LbSWoonqhH7HA1+ZRO+rMIvFs=
|
||||||
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
@@ -390,8 +390,6 @@ github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbd
|
|||||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM=
|
|
||||||
github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
|
|
||||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||||
@@ -501,8 +499,8 @@ github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9ax
|
|||||||
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||||
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45 h1:ujgviVYmx243Ksy7NdSwrdGPSRNE3pb8kEDSpH0QuAQ=
|
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45 h1:ujgviVYmx243Ksy7NdSwrdGPSRNE3pb8kEDSpH0QuAQ=
|
||||||
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45/go.mod h1:5/sjFmLb8O96B5737VCqhHyGRzNFIaN/Bu7ZodXc3qQ=
|
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45/go.mod h1:5/sjFmLb8O96B5737VCqhHyGRzNFIaN/Bu7ZodXc3qQ=
|
||||||
github.com/netbirdio/wireguard-go v0.0.0-20260523085312-4b4a4e36017f h1:ff2D57RBjWtyQ2wVwJOxOgXAXOe/J2lJWtSX0Bz/BRk=
|
github.com/netbirdio/wireguard-go v0.0.0-20260107100953-33b7c9d03db0 h1:h/QnNzm7xzHPm+gajcblYUOclrW2FeNeDlUNj6tTWKQ=
|
||||||
github.com/netbirdio/wireguard-go v0.0.0-20260523085312-4b4a4e36017f/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
github.com/netbirdio/wireguard-go v0.0.0-20260107100953-33b7c9d03db0/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||||
@@ -902,8 +900,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func (c *Controller) CountStreams() int {
|
|||||||
return c.peersUpdateManager.CountStreams()
|
return c.peersUpdateManager.CountStreams()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID string, reason types.UpdateReason) error {
|
func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID string) error {
|
||||||
log.WithContext(ctx).Tracef("updating peers for account %s from %s", accountID, util.GetCallerName())
|
log.WithContext(ctx).Tracef("updating peers for account %s from %s", accountID, util.GetCallerName())
|
||||||
account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID)
|
account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -175,10 +175,6 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.accountManagerMetrics != nil {
|
|
||||||
c.accountManagerMetrics.CountNmapTriggered(string(reason.Resource), string(reason.Operation))
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
semaphore <- struct{}{}
|
semaphore <- struct{}{}
|
||||||
go func(p *nbpeer.Peer) {
|
go func(p *nbpeer.Peer) {
|
||||||
@@ -246,14 +242,14 @@ func (c *Controller) bufferSendUpdateAccountPeers(ctx context.Context, accountID
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
_ = c.sendUpdateAccountPeers(ctx, accountID, reason)
|
_ = c.sendUpdateAccountPeers(ctx, accountID)
|
||||||
if !b.update.Load() {
|
if !b.update.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.update.Store(false)
|
b.update.Store(false)
|
||||||
if b.next == nil {
|
if b.next == nil {
|
||||||
b.next = time.AfterFunc(time.Duration(c.updateAccountPeersBufferInterval.Load()), func() {
|
b.next = time.AfterFunc(time.Duration(c.updateAccountPeersBufferInterval.Load()), func() {
|
||||||
_ = c.sendUpdateAccountPeers(ctx, accountID, reason)
|
_ = c.sendUpdateAccountPeers(ctx, accountID)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -269,7 +265,7 @@ func (c *Controller) UpdateAccountPeers(ctx context.Context, accountID string, r
|
|||||||
if c.accountManagerMetrics != nil {
|
if c.accountManagerMetrics != nil {
|
||||||
c.accountManagerMetrics.CountUpdateAccountPeersTriggered(string(reason.Resource), string(reason.Operation))
|
c.accountManagerMetrics.CountUpdateAccountPeersTriggered(string(reason.Resource), string(reason.Operation))
|
||||||
}
|
}
|
||||||
return c.sendUpdateAccountPeers(ctx, accountID, reason)
|
return c.sendUpdateAccountPeers(ctx, accountID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) UpdateAccountPeer(ctx context.Context, accountId string, peerId string) error {
|
func (c *Controller) UpdateAccountPeer(ctx context.Context, accountId string, peerId string) error {
|
||||||
@@ -363,14 +359,14 @@ func (c *Controller) BufferUpdateAccountPeers(ctx context.Context, accountID str
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
_ = c.sendUpdateAccountPeers(ctx, accountID, reason)
|
_ = c.sendUpdateAccountPeers(ctx, accountID)
|
||||||
if !b.update.Load() {
|
if !b.update.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.update.Store(false)
|
b.update.Store(false)
|
||||||
if b.next == nil {
|
if b.next == nil {
|
||||||
b.next = time.AfterFunc(time.Duration(c.updateAccountPeersBufferInterval.Load()), func() {
|
b.next = time.AfterFunc(time.Duration(c.updateAccountPeersBufferInterval.Load()), func() {
|
||||||
_ = c.sendUpdateAccountPeers(ctx, accountID, reason)
|
_ = c.sendUpdateAccountPeers(ctx, accountID)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func (p *PeersUpdateManager) SendUpdate(ctx context.Context, peerID string, upda
|
|||||||
found = true
|
found = true
|
||||||
select {
|
select {
|
||||||
case channel <- update:
|
case channel <- update:
|
||||||
log.WithContext(ctx).Tracef("update was sent to channel for peer %s", peerID)
|
log.WithContext(ctx).Debugf("update was sent to channel for peer %s", peerID)
|
||||||
default:
|
default:
|
||||||
dropped = true
|
dropped = true
|
||||||
log.WithContext(ctx).Warnf("channel for peer %s is %d full or closed", peerID, len(channel))
|
log.WithContext(ctx).Warnf("channel for peer %s is %d full or closed", peerID, len(channel))
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package peers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
@@ -36,14 +35,6 @@ type Manager interface {
|
|||||||
SetAccountManager(accountManager account.Manager)
|
SetAccountManager(accountManager account.Manager)
|
||||||
GetPeerID(ctx context.Context, peerKey string) (string, error)
|
GetPeerID(ctx context.Context, peerKey string) (string, error)
|
||||||
CreateProxyPeer(ctx context.Context, accountID string, peerKey string, cluster string) error
|
CreateProxyPeer(ctx context.Context, accountID string, peerKey string, cluster string) error
|
||||||
// GetPeerByTunnelIP looks up a peer in accountID by its WireGuard tunnel IP.
|
|
||||||
// Returns nil with an error when no match exists. No permission check;
|
|
||||||
// callers (the proxy's ValidateTunnelPeer RPC) are trusted server components.
|
|
||||||
GetPeerByTunnelIP(ctx context.Context, accountID string, ip net.IP) (*peer.Peer, error)
|
|
||||||
// GetPeerWithGroups returns the peer and the list of *types.Group it belongs
|
|
||||||
// to. Used by the proxy's auth path to authorise a request by the calling
|
|
||||||
// peer's group memberships.
|
|
||||||
GetPeerWithGroups(ctx context.Context, accountID, peerID string) (*peer.Peer, []*types.Group, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type managerImpl struct {
|
type managerImpl struct {
|
||||||
@@ -75,7 +66,7 @@ func (m *managerImpl) SetAccountManager(accountManager account.Manager) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerImpl) GetPeer(ctx context.Context, accountID, userID, peerID string) (*peer.Peer, error) {
|
func (m *managerImpl) GetPeer(ctx context.Context, accountID, userID, peerID string) (*peer.Peer, error) {
|
||||||
allowed, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Read)
|
allowed, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to validate user permissions: %w", err)
|
return nil, fmt.Errorf("failed to validate user permissions: %w", err)
|
||||||
}
|
}
|
||||||
@@ -88,7 +79,7 @@ func (m *managerImpl) GetPeer(ctx context.Context, accountID, userID, peerID str
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerImpl) GetAllPeers(ctx context.Context, accountID, userID string) ([]*peer.Peer, error) {
|
func (m *managerImpl) GetAllPeers(ctx context.Context, accountID, userID string) ([]*peer.Peer, error) {
|
||||||
allowed, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Read)
|
allowed, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to validate user permissions: %w", err)
|
return nil, fmt.Errorf("failed to validate user permissions: %w", err)
|
||||||
}
|
}
|
||||||
@@ -108,26 +99,6 @@ func (m *managerImpl) GetPeersByGroupIDs(ctx context.Context, accountID string,
|
|||||||
return m.store.GetPeersByGroupIDs(ctx, accountID, groupsIDs)
|
return m.store.GetPeersByGroupIDs(ctx, accountID, groupsIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerByTunnelIP delegates to the store's indexed lookup.
|
|
||||||
func (m *managerImpl) GetPeerByTunnelIP(ctx context.Context, accountID string, ip net.IP) (*peer.Peer, error) {
|
|
||||||
return m.store.GetPeerByIP(ctx, store.LockingStrengthNone, accountID, ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeerWithGroups returns the peer plus its group memberships. Any store
|
|
||||||
// error returns (nil, nil, err) so callers never receive a valid peer
|
|
||||||
// alongside a non-nil error.
|
|
||||||
func (m *managerImpl) GetPeerWithGroups(ctx context.Context, accountID, peerID string) (*peer.Peer, []*types.Group, error) {
|
|
||||||
p, err := m.store.GetPeerByID(ctx, store.LockingStrengthNone, accountID, peerID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
groups, err := m.store.GetPeerGroups(ctx, store.LockingStrengthNone, accountID, peerID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return p, groups, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *managerImpl) DeletePeers(ctx context.Context, accountID string, peerIDs []string, userID string, checkConnected bool) error {
|
func (m *managerImpl) DeletePeers(ctx context.Context, accountID string, peerIDs []string, userID string, checkConnected bool) error {
|
||||||
settings, err := m.store.GetAccountSettings(ctx, store.LockingStrengthNone, accountID)
|
settings, err := m.store.GetAccountSettings(ctx, store.LockingStrengthNone, accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package peers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
net "net"
|
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
@@ -14,7 +13,6 @@ import (
|
|||||||
account "github.com/netbirdio/netbird/management/server/account"
|
account "github.com/netbirdio/netbird/management/server/account"
|
||||||
integrated_validator "github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
integrated_validator "github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
||||||
peer "github.com/netbirdio/netbird/management/server/peer"
|
peer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
types "github.com/netbirdio/netbird/management/server/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockManager is a mock of Manager interface.
|
// MockManager is a mock of Manager interface.
|
||||||
@@ -40,20 +38,6 @@ func (m *MockManager) EXPECT() *MockManagerMockRecorder {
|
|||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateProxyPeer mocks base method.
|
|
||||||
func (m *MockManager) CreateProxyPeer(ctx context.Context, accountID, peerKey, cluster string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateProxyPeer", ctx, accountID, peerKey, cluster)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateProxyPeer indicates an expected call of CreateProxyPeer.
|
|
||||||
func (mr *MockManagerMockRecorder) CreateProxyPeer(ctx, accountID, peerKey, cluster interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateProxyPeer", reflect.TypeOf((*MockManager)(nil).CreateProxyPeer), ctx, accountID, peerKey, cluster)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeletePeers mocks base method.
|
// DeletePeers mocks base method.
|
||||||
func (m *MockManager) DeletePeers(ctx context.Context, accountID string, peerIDs []string, userID string, checkConnected bool) error {
|
func (m *MockManager) DeletePeers(ctx context.Context, accountID string, peerIDs []string, userID string, checkConnected bool) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -113,21 +97,6 @@ func (mr *MockManagerMockRecorder) GetPeerAccountID(ctx, peerID interface{}) *go
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeerAccountID", reflect.TypeOf((*MockManager)(nil).GetPeerAccountID), ctx, peerID)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeerAccountID", reflect.TypeOf((*MockManager)(nil).GetPeerAccountID), ctx, peerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerByTunnelIP mocks base method.
|
|
||||||
func (m *MockManager) GetPeerByTunnelIP(ctx context.Context, accountID string, ip net.IP) (*peer.Peer, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetPeerByTunnelIP", ctx, accountID, ip)
|
|
||||||
ret0, _ := ret[0].(*peer.Peer)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeerByTunnelIP indicates an expected call of GetPeerByTunnelIP.
|
|
||||||
func (mr *MockManagerMockRecorder) GetPeerByTunnelIP(ctx, accountID, ip interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeerByTunnelIP", reflect.TypeOf((*MockManager)(nil).GetPeerByTunnelIP), ctx, accountID, ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeerID mocks base method.
|
// GetPeerID mocks base method.
|
||||||
func (m *MockManager) GetPeerID(ctx context.Context, peerKey string) (string, error) {
|
func (m *MockManager) GetPeerID(ctx context.Context, peerKey string) (string, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -143,22 +112,6 @@ func (mr *MockManagerMockRecorder) GetPeerID(ctx, peerKey interface{}) *gomock.C
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeerID", reflect.TypeOf((*MockManager)(nil).GetPeerID), ctx, peerKey)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeerID", reflect.TypeOf((*MockManager)(nil).GetPeerID), ctx, peerKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerWithGroups mocks base method.
|
|
||||||
func (m *MockManager) GetPeerWithGroups(ctx context.Context, accountID, peerID string) (*peer.Peer, []*types.Group, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetPeerWithGroups", ctx, accountID, peerID)
|
|
||||||
ret0, _ := ret[0].(*peer.Peer)
|
|
||||||
ret1, _ := ret[1].([]*types.Group)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeerWithGroups indicates an expected call of GetPeerWithGroups.
|
|
||||||
func (mr *MockManagerMockRecorder) GetPeerWithGroups(ctx, accountID, peerID interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeerWithGroups", reflect.TypeOf((*MockManager)(nil).GetPeerWithGroups), ctx, accountID, peerID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeersByGroupIDs mocks base method.
|
// GetPeersByGroupIDs mocks base method.
|
||||||
func (m *MockManager) GetPeersByGroupIDs(ctx context.Context, accountID string, groupsIDs []string) ([]*peer.Peer, error) {
|
func (m *MockManager) GetPeersByGroupIDs(ctx context.Context, accountID string, groupsIDs []string) ([]*peer.Peer, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -209,3 +162,17 @@ func (mr *MockManagerMockRecorder) SetNetworkMapController(networkMapController
|
|||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNetworkMapController", reflect.TypeOf((*MockManager)(nil).SetNetworkMapController), networkMapController)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNetworkMapController", reflect.TypeOf((*MockManager)(nil).SetNetworkMapController), networkMapController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateProxyPeer mocks base method.
|
||||||
|
func (m *MockManager) CreateProxyPeer(ctx context.Context, accountID string, peerKey string, cluster string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateProxyPeer", ctx, accountID, peerKey, cluster)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateProxyPeer indicates an expected call of CreateProxyPeer.
|
||||||
|
func (mr *MockManagerMockRecorder) CreateProxyPeer(ctx, accountID, peerKey, cluster interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateProxyPeer", reflect.TypeOf((*MockManager)(nil).CreateProxyPeer), ctx, accountID, peerKey, cluster)
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func (m *managerImpl) SaveAccessLog(ctx context.Context, logEntry *accesslogs.Ac
|
|||||||
|
|
||||||
// GetAllAccessLogs retrieves access logs for an account with pagination and filtering
|
// GetAllAccessLogs retrieves access logs for an account with pagination and filtering
|
||||||
func (m *managerImpl) GetAllAccessLogs(ctx context.Context, accountID, userID string, filter *accesslogs.AccessLogFilter) ([]*accesslogs.AccessLogEntry, int64, error) {
|
func (m *managerImpl) GetAllAccessLogs(ctx context.Context, accountID, userID string, filter *accesslogs.AccessLogFilter) ([]*accesslogs.AccessLogEntry, int64, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, status.NewPermissionValidationError(err)
|
return nil, 0, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ type Domain struct {
|
|||||||
// SupportsCrowdSec is populated at query time from proxy cluster capabilities.
|
// SupportsCrowdSec is populated at query time from proxy cluster capabilities.
|
||||||
// Not persisted.
|
// Not persisted.
|
||||||
SupportsCrowdSec *bool `gorm:"-"`
|
SupportsCrowdSec *bool `gorm:"-"`
|
||||||
// SupportsPrivate is populated at query time from proxy cluster capabilities. Not persisted.
|
|
||||||
SupportsPrivate *bool `gorm:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventMeta returns activity event metadata for a domain
|
// EventMeta returns activity event metadata for a domain
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ func domainToApi(d *domain.Domain) api.ReverseProxyDomain {
|
|||||||
SupportsCustomPorts: d.SupportsCustomPorts,
|
SupportsCustomPorts: d.SupportsCustomPorts,
|
||||||
RequireSubdomain: d.RequireSubdomain,
|
RequireSubdomain: d.RequireSubdomain,
|
||||||
SupportsCrowdsec: d.SupportsCrowdSec,
|
SupportsCrowdsec: d.SupportsCrowdSec,
|
||||||
SupportsPrivate: d.SupportsPrivate,
|
|
||||||
}
|
}
|
||||||
if d.TargetCluster != "" {
|
if d.TargetCluster != "" {
|
||||||
resp.TargetCluster = &d.TargetCluster
|
resp.TargetCluster = &d.TargetCluster
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ type proxyManager interface {
|
|||||||
ClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
ClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
||||||
ClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
ClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
||||||
ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
||||||
ClusterSupportsPrivate(ctx context.Context, clusterAddr string) *bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@@ -57,7 +56,7 @@ func NewManager(store store, proxyMgr proxyManager, permissionsManager permissio
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) GetDomains(ctx context.Context, accountID, userID string) ([]*domain.Domain, error) {
|
func (m Manager) GetDomains(ctx context.Context, accountID, userID string) ([]*domain.Domain, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -94,7 +93,6 @@ func (m Manager) GetDomains(ctx context.Context, accountID, userID string) ([]*d
|
|||||||
d.SupportsCustomPorts = m.proxyManager.ClusterSupportsCustomPorts(ctx, cluster)
|
d.SupportsCustomPorts = m.proxyManager.ClusterSupportsCustomPorts(ctx, cluster)
|
||||||
d.RequireSubdomain = m.proxyManager.ClusterRequireSubdomain(ctx, cluster)
|
d.RequireSubdomain = m.proxyManager.ClusterRequireSubdomain(ctx, cluster)
|
||||||
d.SupportsCrowdSec = m.proxyManager.ClusterSupportsCrowdSec(ctx, cluster)
|
d.SupportsCrowdSec = m.proxyManager.ClusterSupportsCrowdSec(ctx, cluster)
|
||||||
d.SupportsPrivate = m.proxyManager.ClusterSupportsPrivate(ctx, cluster)
|
|
||||||
ret = append(ret, d)
|
ret = append(ret, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +109,6 @@ func (m Manager) GetDomains(ctx context.Context, accountID, userID string) ([]*d
|
|||||||
if d.TargetCluster != "" {
|
if d.TargetCluster != "" {
|
||||||
cd.SupportsCustomPorts = m.proxyManager.ClusterSupportsCustomPorts(ctx, d.TargetCluster)
|
cd.SupportsCustomPorts = m.proxyManager.ClusterSupportsCustomPorts(ctx, d.TargetCluster)
|
||||||
cd.SupportsCrowdSec = m.proxyManager.ClusterSupportsCrowdSec(ctx, d.TargetCluster)
|
cd.SupportsCrowdSec = m.proxyManager.ClusterSupportsCrowdSec(ctx, d.TargetCluster)
|
||||||
cd.SupportsPrivate = m.proxyManager.ClusterSupportsPrivate(ctx, d.TargetCluster)
|
|
||||||
}
|
}
|
||||||
// Custom domains never require a subdomain by default since
|
// Custom domains never require a subdomain by default since
|
||||||
// the account owns them and should be able to use the bare domain.
|
// the account owns them and should be able to use the bare domain.
|
||||||
@@ -122,7 +119,7 @@ func (m Manager) GetDomains(ctx context.Context, accountID, userID string) ([]*d
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) CreateDomain(ctx context.Context, accountID, userID, domainName, targetCluster string) (*domain.Domain, error) {
|
func (m Manager) CreateDomain(ctx context.Context, accountID, userID, domainName, targetCluster string) (*domain.Domain, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Create)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Create)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -163,7 +160,7 @@ func (m Manager) CreateDomain(ctx context.Context, accountID, userID, domainName
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) DeleteDomain(ctx context.Context, accountID, userID, domainID string) error {
|
func (m Manager) DeleteDomain(ctx context.Context, accountID, userID, domainID string) error {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Delete)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Delete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.NewPermissionValidationError(err)
|
return status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -187,7 +184,7 @@ func (m Manager) DeleteDomain(ctx context.Context, accountID, userID, domainID s
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) ValidateDomain(ctx context.Context, accountID, userID, domainID string) {
|
func (m Manager) ValidateDomain(ctx context.Context, accountID, userID, domainID string) {
|
||||||
ok, _, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Create)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Create)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"accountID": accountID,
|
"accountID": accountID,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type mockProxyManager struct {
|
type mockProxyManager struct {
|
||||||
getActiveClusterAddressesFunc func(ctx context.Context) ([]string, error)
|
getActiveClusterAddressesFunc func(ctx context.Context) ([]string, error)
|
||||||
getActiveClusterAddressesForAccountFunc func(ctx context.Context, accountID string) ([]string, error)
|
getActiveClusterAddressesForAccountFunc func(ctx context.Context, accountID string) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,10 +40,6 @@ func (m *mockProxyManager) ClusterSupportsCrowdSec(_ context.Context, _ string)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockProxyManager) ClusterSupportsPrivate(_ context.Context, _ string) *bool {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetClusterAllowList_BYOPMergedWithPublic(t *testing.T) {
|
func TestGetClusterAllowList_BYOPMergedWithPublic(t *testing.T) {
|
||||||
pm := &mockProxyManager{
|
pm := &mockProxyManager{
|
||||||
getActiveClusterAddressesForAccountFunc: func(_ context.Context, accID string) ([]string, error) {
|
getActiveClusterAddressesForAccountFunc: func(_ context.Context, accID string) ([]string, error) {
|
||||||
@@ -155,3 +151,4 @@ func TestGetClusterAllowList_PublicEmpty_BYOPOnly(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, []string{"byop.example.com"}, result)
|
assert.Equal(t, []string{"byop.example.com"}, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ type Manager interface {
|
|||||||
ClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
ClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
||||||
ClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
ClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
||||||
ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
||||||
ClusterSupportsPrivate(ctx context.Context, clusterAddr string) *bool
|
|
||||||
CleanupStale(ctx context.Context, inactivityDuration time.Duration) error
|
CleanupStale(ctx context.Context, inactivityDuration time.Duration) error
|
||||||
GetAccountProxy(ctx context.Context, accountID string) (*Proxy, error)
|
GetAccountProxy(ctx context.Context, accountID string) (*Proxy, error)
|
||||||
CountAccountProxies(ctx context.Context, accountID string) (int64, error)
|
CountAccountProxies(ctx context.Context, accountID string) (int64, error)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ type store interface {
|
|||||||
GetClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
GetClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
||||||
GetClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
GetClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
||||||
GetClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
GetClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
||||||
GetClusterSupportsPrivate(ctx context.Context, clusterAddr string) *bool
|
|
||||||
CleanupStaleProxies(ctx context.Context, inactivityDuration time.Duration) error
|
CleanupStaleProxies(ctx context.Context, inactivityDuration time.Duration) error
|
||||||
GetProxyByAccountID(ctx context.Context, accountID string) (*proxy.Proxy, error)
|
GetProxyByAccountID(ctx context.Context, accountID string) (*proxy.Proxy, error)
|
||||||
CountProxiesByAccountID(ctx context.Context, accountID string) (int64, error)
|
CountProxiesByAccountID(ctx context.Context, accountID string) (int64, error)
|
||||||
@@ -138,11 +137,6 @@ func (m Manager) ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string
|
|||||||
return m.store.GetClusterSupportsCrowdSec(ctx, clusterAddr)
|
return m.store.GetClusterSupportsCrowdSec(ctx, clusterAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClusterSupportsPrivate reports whether any active proxy claims the private capability (nil = unreported).
|
|
||||||
func (m Manager) ClusterSupportsPrivate(ctx context.Context, clusterAddr string) *bool {
|
|
||||||
return m.store.GetClusterSupportsPrivate(ctx, clusterAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanupStale removes proxies that haven't sent heartbeat in the specified duration
|
// CleanupStale removes proxies that haven't sent heartbeat in the specified duration
|
||||||
func (m *Manager) CleanupStale(ctx context.Context, inactivityDuration time.Duration) error {
|
func (m *Manager) CleanupStale(ctx context.Context, inactivityDuration time.Duration) error {
|
||||||
if err := m.store.CleanupStaleProxies(ctx, inactivityDuration); err != nil {
|
if err := m.store.CleanupStaleProxies(ctx, inactivityDuration); err != nil {
|
||||||
@@ -184,3 +178,4 @@ func (m *Manager) DeleteAccountCluster(ctx context.Context, clusterAddress, acco
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type mockStore struct {
|
type mockStore struct {
|
||||||
saveProxyFunc func(ctx context.Context, p *proxy.Proxy) error
|
saveProxyFunc func(ctx context.Context, p *proxy.Proxy) error
|
||||||
disconnectProxyFunc func(ctx context.Context, proxyID, sessionID string) error
|
disconnectProxyFunc func(ctx context.Context, proxyID, sessionID string) error
|
||||||
updateProxyHeartbeatFunc func(ctx context.Context, p *proxy.Proxy) error
|
updateProxyHeartbeatFunc func(ctx context.Context, p *proxy.Proxy) error
|
||||||
getActiveProxyClusterAddressesFunc func(ctx context.Context) ([]string, error)
|
getActiveProxyClusterAddressesFunc func(ctx context.Context) ([]string, error)
|
||||||
getActiveProxyClusterAddressesForAccFunc func(ctx context.Context, accountID string) ([]string, error)
|
getActiveProxyClusterAddressesForAccFunc func(ctx context.Context, accountID string) ([]string, error)
|
||||||
cleanupStaleProxiesFunc func(ctx context.Context, d time.Duration) error
|
cleanupStaleProxiesFunc func(ctx context.Context, d time.Duration) error
|
||||||
getProxyByAccountIDFunc func(ctx context.Context, accountID string) (*proxy.Proxy, error)
|
getProxyByAccountIDFunc func(ctx context.Context, accountID string) (*proxy.Proxy, error)
|
||||||
countProxiesByAccountIDFunc func(ctx context.Context, accountID string) (int64, error)
|
countProxiesByAccountIDFunc func(ctx context.Context, accountID string) (int64, error)
|
||||||
isClusterAddressConflictingFunc func(ctx context.Context, clusterAddress, accountID string) (bool, error)
|
isClusterAddressConflictingFunc func(ctx context.Context, clusterAddress, accountID string) (bool, error)
|
||||||
deleteAccountClusterFunc func(ctx context.Context, clusterAddress, accountID string) error
|
deleteAccountClusterFunc func(ctx context.Context, clusterAddress, accountID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockStore) SaveProxy(ctx context.Context, p *proxy.Proxy) error {
|
func (m *mockStore) SaveProxy(ctx context.Context, p *proxy.Proxy) error {
|
||||||
@@ -99,9 +99,6 @@ func (m *mockStore) GetClusterRequireSubdomain(_ context.Context, _ string) *boo
|
|||||||
func (m *mockStore) GetClusterSupportsCrowdSec(_ context.Context, _ string) *bool {
|
func (m *mockStore) GetClusterSupportsCrowdSec(_ context.Context, _ string) *bool {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (m *mockStore) GetClusterSupportsPrivate(_ context.Context, _ string) *bool {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestManager(s store) *Manager {
|
func newTestManager(s store) *Manager {
|
||||||
meter := noop.NewMeterProvider().Meter("test")
|
meter := noop.NewMeterProvider().Meter("test")
|
||||||
|
|||||||
@@ -92,20 +92,6 @@ func (mr *MockManagerMockRecorder) ClusterSupportsCrowdSec(ctx, clusterAddr inte
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterSupportsCrowdSec", reflect.TypeOf((*MockManager)(nil).ClusterSupportsCrowdSec), ctx, clusterAddr)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterSupportsCrowdSec", reflect.TypeOf((*MockManager)(nil).ClusterSupportsCrowdSec), ctx, clusterAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClusterSupportsPrivate mocks base method.
|
|
||||||
func (m *MockManager) ClusterSupportsPrivate(ctx context.Context, clusterAddr string) *bool {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ClusterSupportsPrivate", ctx, clusterAddr)
|
|
||||||
ret0, _ := ret[0].(*bool)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClusterSupportsPrivate indicates an expected call of ClusterSupportsPrivate.
|
|
||||||
func (mr *MockManagerMockRecorder) ClusterSupportsPrivate(ctx, clusterAddr interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterSupportsPrivate", reflect.TypeOf((*MockManager)(nil).ClusterSupportsPrivate), ctx, clusterAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect mocks base method.
|
// Connect mocks base method.
|
||||||
func (m *MockManager) Connect(ctx context.Context, proxyID, sessionID, clusterAddress, ipAddress string, accountID *string, capabilities *Capabilities) (*Proxy, error) {
|
func (m *MockManager) Connect(ctx context.Context, proxyID, sessionID, clusterAddress, ipAddress string, accountID *string, capabilities *Capabilities) (*Proxy, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ type Capabilities struct {
|
|||||||
RequireSubdomain *bool
|
RequireSubdomain *bool
|
||||||
// SupportsCrowdsec indicates whether this proxy has CrowdSec configured.
|
// SupportsCrowdsec indicates whether this proxy has CrowdSec configured.
|
||||||
SupportsCrowdsec *bool
|
SupportsCrowdsec *bool
|
||||||
// Private indicates whether this proxy supports inbound access via Wireguard
|
|
||||||
// tunnel and netbird-only authentication policies
|
|
||||||
Private *bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy represents a reverse proxy instance
|
// Proxy represents a reverse proxy instance
|
||||||
@@ -70,9 +67,10 @@ type Cluster struct {
|
|||||||
Type ClusterType
|
Type ClusterType
|
||||||
Online bool
|
Online bool
|
||||||
ConnectedProxies int
|
ConnectedProxies int
|
||||||
// *bool: nil = no proxy reported the capability; the dashboard renders that as unknown.
|
// Capability flags. *bool because nil means "no proxy reported a
|
||||||
|
// capability for this cluster" — the dashboard renders these as
|
||||||
|
// unknown rather than false.
|
||||||
SupportsCustomPorts *bool
|
SupportsCustomPorts *bool
|
||||||
RequireSubdomain *bool
|
RequireSubdomain *bool
|
||||||
SupportsCrowdSec *bool
|
SupportsCrowdSec *bool
|
||||||
Private *bool
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func (h *handler) createToken(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, ctx, err := h.permissionsManager.ValidateUserPermissions(r.Context(), userAuth.AccountId, userAuth.UserId, modules.Services, operations.Create)
|
ok, err := h.permissionsManager.ValidateUserPermissions(r.Context(), userAuth.AccountId, userAuth.UserId, modules.Services, operations.Create)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteErrorResponse("failed to validate permissions", http.StatusInternalServerError, w)
|
util.WriteErrorResponse("failed to validate permissions", http.StatusInternalServerError, w)
|
||||||
return
|
return
|
||||||
@@ -76,13 +76,13 @@ func (h *handler) createToken(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.store.SaveProxyAccessToken(ctx, &generated.ProxyAccessToken); err != nil {
|
if err := h.store.SaveProxyAccessToken(r.Context(), &generated.ProxyAccessToken); err != nil {
|
||||||
util.WriteErrorResponse("failed to save token", http.StatusInternalServerError, w)
|
util.WriteErrorResponse("failed to save token", http.StatusInternalServerError, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := toProxyTokenCreatedResponse(generated)
|
resp := toProxyTokenCreatedResponse(generated)
|
||||||
util.WriteJSONObject(ctx, w, resp)
|
util.WriteJSONObject(r.Context(), w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) listTokens(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) listTokens(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -92,7 +92,7 @@ func (h *handler) listTokens(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, ctx, err := h.permissionsManager.ValidateUserPermissions(r.Context(), userAuth.AccountId, userAuth.UserId, modules.Services, operations.Read)
|
ok, err := h.permissionsManager.ValidateUserPermissions(r.Context(), userAuth.AccountId, userAuth.UserId, modules.Services, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteErrorResponse("failed to validate permissions", http.StatusInternalServerError, w)
|
util.WriteErrorResponse("failed to validate permissions", http.StatusInternalServerError, w)
|
||||||
return
|
return
|
||||||
@@ -102,7 +102,7 @@ func (h *handler) listTokens(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens, err := h.store.GetProxyAccessTokensByAccountID(ctx, store.LockingStrengthNone, userAuth.AccountId)
|
tokens, err := h.store.GetProxyAccessTokensByAccountID(r.Context(), store.LockingStrengthNone, userAuth.AccountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteErrorResponse("failed to list tokens", http.StatusInternalServerError, w)
|
util.WriteErrorResponse("failed to list tokens", http.StatusInternalServerError, w)
|
||||||
return
|
return
|
||||||
@@ -113,7 +113,7 @@ func (h *handler) listTokens(w http.ResponseWriter, r *http.Request) {
|
|||||||
resp = append(resp, toProxyTokenResponse(token))
|
resp = append(resp, toProxyTokenResponse(token))
|
||||||
}
|
}
|
||||||
|
|
||||||
util.WriteJSONObject(ctx, w, resp)
|
util.WriteJSONObject(r.Context(), w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) revokeToken(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) revokeToken(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -123,7 +123,7 @@ func (h *handler) revokeToken(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, ctx, err := h.permissionsManager.ValidateUserPermissions(r.Context(), userAuth.AccountId, userAuth.UserId, modules.Services, operations.Delete)
|
ok, err := h.permissionsManager.ValidateUserPermissions(r.Context(), userAuth.AccountId, userAuth.UserId, modules.Services, operations.Delete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteErrorResponse("failed to validate permissions", http.StatusInternalServerError, w)
|
util.WriteErrorResponse("failed to validate permissions", http.StatusInternalServerError, w)
|
||||||
return
|
return
|
||||||
@@ -139,7 +139,7 @@ func (h *handler) revokeToken(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := h.store.GetProxyAccessTokenByID(ctx, store.LockingStrengthNone, tokenID)
|
token, err := h.store.GetProxyAccessTokenByID(r.Context(), store.LockingStrengthNone, tokenID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if s, ok := status.FromError(err); ok && s.ErrorType == status.NotFound {
|
if s, ok := status.FromError(err); ok && s.ErrorType == status.NotFound {
|
||||||
util.WriteErrorResponse("token not found", http.StatusNotFound, w)
|
util.WriteErrorResponse("token not found", http.StatusNotFound, w)
|
||||||
@@ -154,12 +154,12 @@ func (h *handler) revokeToken(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.store.RevokeProxyAccessToken(ctx, tokenID); err != nil {
|
if err := h.store.RevokeProxyAccessToken(r.Context(), tokenID); err != nil {
|
||||||
util.WriteErrorResponse("failed to revoke token", http.StatusInternalServerError, w)
|
util.WriteErrorResponse("failed to revoke token", http.StatusInternalServerError, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
util.WriteJSONObject(ctx, w, util.EmptyObject{})
|
util.WriteJSONObject(r.Context(), w, util.EmptyObject{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func toProxyTokenResponse(token *types.ProxyAccessToken) api.ProxyToken {
|
func toProxyTokenResponse(token *types.ProxyAccessToken) api.ProxyToken {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func TestCreateToken_AccountScoped(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
permsMgr := permissions.NewMockManager(ctrl)
|
permsMgr := permissions.NewMockManager(ctrl)
|
||||||
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), accountID, "user-1", modules.Services, operations.Create).Return(true, context.Background(), nil)
|
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), accountID, "user-1", modules.Services, operations.Create).Return(true, nil)
|
||||||
|
|
||||||
h := &handler{
|
h := &handler{
|
||||||
store: mockStore,
|
store: mockStore,
|
||||||
@@ -90,7 +90,7 @@ func TestCreateToken_WithExpiration(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
permsMgr := permissions.NewMockManager(ctrl)
|
permsMgr := permissions.NewMockManager(ctrl)
|
||||||
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), "acc-123", "user-1", modules.Services, operations.Create).Return(true, context.Background(), nil)
|
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), "acc-123", "user-1", modules.Services, operations.Create).Return(true, nil)
|
||||||
|
|
||||||
h := &handler{
|
h := &handler{
|
||||||
store: mockStore,
|
store: mockStore,
|
||||||
@@ -115,7 +115,7 @@ func TestCreateToken_EmptyName(t *testing.T) {
|
|||||||
defer ctrl.Finish()
|
defer ctrl.Finish()
|
||||||
|
|
||||||
permsMgr := permissions.NewMockManager(ctrl)
|
permsMgr := permissions.NewMockManager(ctrl)
|
||||||
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), "acc-123", "user-1", modules.Services, operations.Create).Return(true, context.Background(), nil)
|
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), "acc-123", "user-1", modules.Services, operations.Create).Return(true, nil)
|
||||||
|
|
||||||
h := &handler{
|
h := &handler{
|
||||||
permissionsManager: permsMgr,
|
permissionsManager: permsMgr,
|
||||||
@@ -135,7 +135,7 @@ func TestCreateToken_PermissionDenied(t *testing.T) {
|
|||||||
defer ctrl.Finish()
|
defer ctrl.Finish()
|
||||||
|
|
||||||
permsMgr := permissions.NewMockManager(ctrl)
|
permsMgr := permissions.NewMockManager(ctrl)
|
||||||
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), "acc-123", "user-1", modules.Services, operations.Create).Return(false, context.Background(), nil)
|
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), "acc-123", "user-1", modules.Services, operations.Create).Return(false, nil)
|
||||||
|
|
||||||
h := &handler{
|
h := &handler{
|
||||||
permissionsManager: permsMgr,
|
permissionsManager: permsMgr,
|
||||||
@@ -164,7 +164,7 @@ func TestListTokens(t *testing.T) {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
permsMgr := permissions.NewMockManager(ctrl)
|
permsMgr := permissions.NewMockManager(ctrl)
|
||||||
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), accountID, "user-1", modules.Services, operations.Read).Return(true, context.Background(), nil)
|
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), accountID, "user-1", modules.Services, operations.Read).Return(true, nil)
|
||||||
|
|
||||||
h := &handler{
|
h := &handler{
|
||||||
store: mockStore,
|
store: mockStore,
|
||||||
@@ -202,7 +202,7 @@ func TestRevokeToken_Success(t *testing.T) {
|
|||||||
mockStore.EXPECT().RevokeProxyAccessToken(gomock.Any(), "tok-1").Return(nil)
|
mockStore.EXPECT().RevokeProxyAccessToken(gomock.Any(), "tok-1").Return(nil)
|
||||||
|
|
||||||
permsMgr := permissions.NewMockManager(ctrl)
|
permsMgr := permissions.NewMockManager(ctrl)
|
||||||
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), accountID, "user-1", modules.Services, operations.Delete).Return(true, context.Background(), nil)
|
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), accountID, "user-1", modules.Services, operations.Delete).Return(true, nil)
|
||||||
|
|
||||||
h := &handler{
|
h := &handler{
|
||||||
store: mockStore,
|
store: mockStore,
|
||||||
@@ -231,7 +231,7 @@ func TestRevokeToken_WrongAccount(t *testing.T) {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
permsMgr := permissions.NewMockManager(ctrl)
|
permsMgr := permissions.NewMockManager(ctrl)
|
||||||
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), "acc-123", "user-1", modules.Services, operations.Delete).Return(true, context.Background(), nil)
|
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), "acc-123", "user-1", modules.Services, operations.Delete).Return(true, nil)
|
||||||
|
|
||||||
h := &handler{
|
h := &handler{
|
||||||
store: mockStore,
|
store: mockStore,
|
||||||
@@ -258,7 +258,7 @@ func TestRevokeToken_ManagementWideToken(t *testing.T) {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
permsMgr := permissions.NewMockManager(ctrl)
|
permsMgr := permissions.NewMockManager(ctrl)
|
||||||
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), "acc-123", "user-1", modules.Services, operations.Delete).Return(true, context.Background(), nil)
|
permsMgr.EXPECT().ValidateUserPermissions(gomock.Any(), "acc-123", "user-1", modules.Services, operations.Delete).Return(true, nil)
|
||||||
|
|
||||||
h := &handler{
|
h := &handler{
|
||||||
store: mockStore,
|
store: mockStore,
|
||||||
|
|||||||
@@ -204,7 +204,6 @@ func (h *handler) getClusters(w http.ResponseWriter, r *http.Request) {
|
|||||||
SupportsCustomPorts: c.SupportsCustomPorts,
|
SupportsCustomPorts: c.SupportsCustomPorts,
|
||||||
RequireSubdomain: c.RequireSubdomain,
|
RequireSubdomain: c.RequireSubdomain,
|
||||||
SupportsCrowdsec: c.SupportsCrowdSec,
|
SupportsCrowdsec: c.SupportsCrowdSec,
|
||||||
Private: c.Private,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ type CapabilityProvider interface {
|
|||||||
ClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
ClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
||||||
ClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
ClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
||||||
ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
||||||
ClusterSupportsPrivate(ctx context.Context, clusterAddr string) *bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@@ -120,7 +119,7 @@ func (m *Manager) StartExposeReaper(ctx context.Context) {
|
|||||||
// capability flags reported by its active proxies so the dashboard can
|
// capability flags reported by its active proxies so the dashboard can
|
||||||
// render feature support without a second round-trip.
|
// render feature support without a second round-trip.
|
||||||
func (m *Manager) GetClusters(ctx context.Context, accountID, userID string) ([]proxy.Cluster, error) {
|
func (m *Manager) GetClusters(ctx context.Context, accountID, userID string) ([]proxy.Cluster, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -137,7 +136,6 @@ func (m *Manager) GetClusters(ctx context.Context, accountID, userID string) ([]
|
|||||||
clusters[i].SupportsCustomPorts = m.capabilities.ClusterSupportsCustomPorts(ctx, clusters[i].Address)
|
clusters[i].SupportsCustomPorts = m.capabilities.ClusterSupportsCustomPorts(ctx, clusters[i].Address)
|
||||||
clusters[i].RequireSubdomain = m.capabilities.ClusterRequireSubdomain(ctx, clusters[i].Address)
|
clusters[i].RequireSubdomain = m.capabilities.ClusterRequireSubdomain(ctx, clusters[i].Address)
|
||||||
clusters[i].SupportsCrowdSec = m.capabilities.ClusterSupportsCrowdSec(ctx, clusters[i].Address)
|
clusters[i].SupportsCrowdSec = m.capabilities.ClusterSupportsCrowdSec(ctx, clusters[i].Address)
|
||||||
clusters[i].Private = m.capabilities.ClusterSupportsPrivate(ctx, clusters[i].Address)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return clusters, nil
|
return clusters, nil
|
||||||
@@ -146,7 +144,7 @@ func (m *Manager) GetClusters(ctx context.Context, accountID, userID string) ([]
|
|||||||
// DeleteAccountCluster removes all proxy registrations for the given cluster address
|
// DeleteAccountCluster removes all proxy registrations for the given cluster address
|
||||||
// owned by the account.
|
// owned by the account.
|
||||||
func (m *Manager) DeleteAccountCluster(ctx context.Context, accountID, userID, clusterAddress string) error {
|
func (m *Manager) DeleteAccountCluster(ctx context.Context, accountID, userID, clusterAddress string) error {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Delete)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Delete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.NewPermissionValidationError(err)
|
return status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -158,7 +156,7 @@ func (m *Manager) DeleteAccountCluster(ctx context.Context, accountID, userID, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetAllServices(ctx context.Context, accountID, userID string) ([]*service.Service, error) {
|
func (m *Manager) GetAllServices(ctx context.Context, accountID, userID string) ([]*service.Service, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -210,9 +208,6 @@ func (m *Manager) replaceHostByLookup(ctx context.Context, accountID string, s *
|
|||||||
target.Host = resource.Domain
|
target.Host = resource.Domain
|
||||||
case service.TargetTypeSubnet:
|
case service.TargetTypeSubnet:
|
||||||
// For subnets we do not do any lookups on the resource
|
// For subnets we do not do any lookups on the resource
|
||||||
case service.TargetTypeCluster:
|
|
||||||
// Cluster targets carry the upstream address on target_id; the
|
|
||||||
// proxy resolves the destination at request time.
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown target type: %s", target.TargetType)
|
return fmt.Errorf("unknown target type: %s", target.TargetType)
|
||||||
}
|
}
|
||||||
@@ -222,7 +217,7 @@ func (m *Manager) replaceHostByLookup(ctx context.Context, accountID string, s *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetService(ctx context.Context, accountID, userID, serviceID string) (*service.Service, error) {
|
func (m *Manager) GetService(ctx context.Context, accountID, userID, serviceID string) (*service.Service, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -243,7 +238,7 @@ func (m *Manager) GetService(ctx context.Context, accountID, userID, serviceID s
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) CreateService(ctx context.Context, accountID, userID string, s *service.Service) (*service.Service, error) {
|
func (m *Manager) CreateService(ctx context.Context, accountID, userID string, s *service.Service) (*service.Service, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Create)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Create)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -528,7 +523,7 @@ func (m *Manager) checkDomainAvailable(ctx context.Context, transaction store.St
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) UpdateService(ctx context.Context, accountID, userID string, service *service.Service) (*service.Service, error) {
|
func (m *Manager) UpdateService(ctx context.Context, accountID, userID string, service *service.Service) (*service.Service, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Update)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -784,10 +779,6 @@ func validateTargetReferences(ctx context.Context, transaction store.Store, acco
|
|||||||
if err := validateResourceTarget(ctx, transaction, accountID, target); err != nil {
|
if err := validateResourceTarget(ctx, transaction, accountID, target); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case service.TargetTypeCluster:
|
|
||||||
if err := validateClusterTarget(target); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return status.Errorf(status.InvalidArgument, "unknown target type %q for target %q", target.TargetType, target.TargetId)
|
return status.Errorf(status.InvalidArgument, "unknown target type %q for target %q", target.TargetType, target.TargetId)
|
||||||
}
|
}
|
||||||
@@ -795,13 +786,6 @@ func validateTargetReferences(ctx context.Context, transaction store.Store, acco
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateClusterTarget(target *service.Target) error {
|
|
||||||
if !target.Options.DirectUpstream {
|
|
||||||
return status.Errorf(status.InvalidArgument, "cluster target %s has direct upstream disabled", target.Host)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validatePeerTarget(ctx context.Context, transaction store.Store, accountID string, target *service.Target) error {
|
func validatePeerTarget(ctx context.Context, transaction store.Store, accountID string, target *service.Target) error {
|
||||||
if _, err := transaction.GetPeerByID(ctx, store.LockingStrengthShare, accountID, target.TargetId); err != nil {
|
if _, err := transaction.GetPeerByID(ctx, store.LockingStrengthShare, accountID, target.TargetId); err != nil {
|
||||||
if sErr, ok := status.FromError(err); ok && sErr.Type() == status.NotFound {
|
if sErr, ok := status.FromError(err); ok && sErr.Type() == status.NotFound {
|
||||||
@@ -836,7 +820,7 @@ func validateResourceTargetType(target *service.Target, resource *resourcetypes.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) DeleteService(ctx context.Context, accountID, userID, serviceID string) error {
|
func (m *Manager) DeleteService(ctx context.Context, accountID, userID, serviceID string) error {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Delete)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Delete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.NewPermissionValidationError(err)
|
return status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -876,7 +860,7 @@ func (m *Manager) DeleteService(ctx context.Context, accountID, userID, serviceI
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) DeleteAllServices(ctx context.Context, accountID, userID string) error {
|
func (m *Manager) DeleteAllServices(ctx context.Context, accountID, userID string) error {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Delete)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Delete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.NewPermissionValidationError(err)
|
return status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -978,14 +962,12 @@ func (m *Manager) ReloadAllServicesForAccount(ctx context.Context, accountID str
|
|||||||
return fmt.Errorf("failed to get services: %w", err)
|
return fmt.Errorf("failed to get services: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
oidcCfg := m.proxyController.GetOIDCValidationConfig()
|
|
||||||
|
|
||||||
for _, s := range services {
|
for _, s := range services {
|
||||||
err = m.replaceHostByLookup(ctx, accountID, s)
|
err = m.replaceHostByLookup(ctx, accountID, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to replace host by lookup for service %s: %w", s.ID, err)
|
return fmt.Errorf("failed to replace host by lookup for service %s: %w", s.ID, err)
|
||||||
}
|
}
|
||||||
m.proxyController.SendServiceUpdateToCluster(ctx, accountID, s.ToProtoMapping(service.Update, "", oidcCfg), s.ProxyCluster)
|
m.proxyController.SendServiceUpdateToCluster(ctx, accountID, s.ToProtoMapping(service.Update, "", m.proxyController.GetOIDCValidationConfig()), s.ProxyCluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1172,7 +1172,7 @@ func TestDeleteService_DeletesTargets(t *testing.T) {
|
|||||||
|
|
||||||
mockPerms.EXPECT().
|
mockPerms.EXPECT().
|
||||||
ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Delete).
|
ValidateUserPermissions(ctx, accountID, userID, modules.Services, operations.Delete).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
mockAcct.EXPECT().
|
mockAcct.EXPECT().
|
||||||
StoreEvent(ctx, userID, service.ID, accountID, activity.ServiceDeleted, gomock.Any())
|
StoreEvent(ctx, userID, service.ID, accountID, activity.ServiceDeleted, gomock.Any())
|
||||||
mockAcct.EXPECT().
|
mockAcct.EXPECT().
|
||||||
@@ -1344,66 +1344,3 @@ func TestValidateSubdomainRequirement(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateTargetReferences_ClusterTargetSkipsLookup(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
mockStore := store.NewMockStore(ctrl)
|
|
||||||
accountID := "test-account"
|
|
||||||
|
|
||||||
// No peer or resource lookups must be issued for cluster targets.
|
|
||||||
targets := []*rpservice.Target{
|
|
||||||
{
|
|
||||||
TargetId: "eu.proxy.netbird.io",
|
|
||||||
TargetType: rpservice.TargetTypeCluster,
|
|
||||||
Options: rpservice.TargetOptions{DirectUpstream: true},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
require.NoError(t, validateTargetReferences(ctx, mockStore, accountID, targets), "cluster target must validate without store lookups")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestValidateTargetReferences_ClusterTargetRequiresDirectUpstream pins the
|
|
||||||
// store-side check that cluster targets must opt into the host-stack dial
|
|
||||||
// path. Without DirectUpstream the proxy would route this target through
|
|
||||||
// the embedded NetBird client and fail on every request.
|
|
||||||
func TestValidateTargetReferences_ClusterTargetRequiresDirectUpstream(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
mockStore := store.NewMockStore(ctrl)
|
|
||||||
accountID := "test-account"
|
|
||||||
|
|
||||||
targets := []*rpservice.Target{
|
|
||||||
{
|
|
||||||
TargetId: "eu.proxy.netbird.io",
|
|
||||||
TargetType: rpservice.TargetTypeCluster,
|
|
||||||
Host: "backend.lan",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err := validateTargetReferences(ctx, mockStore, accountID, targets)
|
|
||||||
require.Error(t, err, "cluster target without direct_upstream must be rejected")
|
|
||||||
assert.ErrorContains(t, err, "direct upstream disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplaceHostByLookup_SkipsClusterTarget(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
mockStore := store.NewMockStore(ctrl)
|
|
||||||
accountID := "test-account"
|
|
||||||
|
|
||||||
mgr := &Manager{store: mockStore}
|
|
||||||
|
|
||||||
svc := &rpservice.Service{
|
|
||||||
ID: "svc-1",
|
|
||||||
AccountID: accountID,
|
|
||||||
Targets: []*rpservice.Target{
|
|
||||||
{
|
|
||||||
TargetId: "eu.proxy.netbird.io",
|
|
||||||
TargetType: rpservice.TargetTypeCluster,
|
|
||||||
Host: "127.0.0.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, mgr.replaceHostByLookup(ctx, accountID, svc), "cluster target must not trigger peer/resource lookup")
|
|
||||||
assert.Equal(t, "127.0.0.1", svc.Targets[0].Host, "operator-supplied host must be preserved for cluster target")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -45,11 +45,10 @@ const (
|
|||||||
StatusCertificateFailed Status = "certificate_failed"
|
StatusCertificateFailed Status = "certificate_failed"
|
||||||
StatusError Status = "error"
|
StatusError Status = "error"
|
||||||
|
|
||||||
TargetTypePeer TargetType = "peer"
|
TargetTypePeer TargetType = "peer"
|
||||||
TargetTypeHost TargetType = "host"
|
TargetTypeHost TargetType = "host"
|
||||||
TargetTypeDomain TargetType = "domain"
|
TargetTypeDomain TargetType = "domain"
|
||||||
TargetTypeSubnet TargetType = "subnet"
|
TargetTypeSubnet TargetType = "subnet"
|
||||||
TargetTypeCluster TargetType = "cluster"
|
|
||||||
|
|
||||||
SourcePermanent = "permanent"
|
SourcePermanent = "permanent"
|
||||||
SourceEphemeral = "ephemeral"
|
SourceEphemeral = "ephemeral"
|
||||||
@@ -61,11 +60,6 @@ type TargetOptions struct {
|
|||||||
SessionIdleTimeout time.Duration `json:"session_idle_timeout,omitempty"`
|
SessionIdleTimeout time.Duration `json:"session_idle_timeout,omitempty"`
|
||||||
PathRewrite PathRewriteMode `json:"path_rewrite,omitempty"`
|
PathRewrite PathRewriteMode `json:"path_rewrite,omitempty"`
|
||||||
CustomHeaders map[string]string `gorm:"serializer:json" json:"custom_headers,omitempty"`
|
CustomHeaders map[string]string `gorm:"serializer:json" json:"custom_headers,omitempty"`
|
||||||
// DirectUpstream bypasses the proxy's embedded NetBird client and dials
|
|
||||||
// the target via the proxy host's network stack. Useful for upstreams
|
|
||||||
// reachable without WireGuard (public APIs, LAN services, localhost
|
|
||||||
// sidecars). Default false.
|
|
||||||
DirectUpstream bool `json:"direct_upstream,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Target struct {
|
type Target struct {
|
||||||
@@ -73,7 +67,7 @@ type Target struct {
|
|||||||
AccountID string `gorm:"index:idx_target_account;not null" json:"-"`
|
AccountID string `gorm:"index:idx_target_account;not null" json:"-"`
|
||||||
ServiceID string `gorm:"index:idx_service_targets;not null" json:"-"`
|
ServiceID string `gorm:"index:idx_service_targets;not null" json:"-"`
|
||||||
Path *string `json:"path,omitempty"`
|
Path *string `json:"path,omitempty"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"` // the Host field is only used for subnet targets, otherwise ignored
|
||||||
Port uint16 `gorm:"index:idx_target_port" json:"port"`
|
Port uint16 `gorm:"index:idx_target_port" json:"port"`
|
||||||
Protocol string `gorm:"index:idx_target_protocol" json:"protocol"`
|
Protocol string `gorm:"index:idx_target_protocol" json:"protocol"`
|
||||||
TargetId string `gorm:"index:idx_target_id" json:"target_id"`
|
TargetId string `gorm:"index:idx_target_id" json:"target_id"`
|
||||||
@@ -206,10 +200,6 @@ type Service struct {
|
|||||||
Mode string `gorm:"default:'http'"`
|
Mode string `gorm:"default:'http'"`
|
||||||
ListenPort uint16
|
ListenPort uint16
|
||||||
PortAutoAssigned bool
|
PortAutoAssigned bool
|
||||||
// Private marks the service as NetBird-only: auth via ValidateTunnelPeer against AccessGroups instead of SSO. HTTP-only.
|
|
||||||
Private bool
|
|
||||||
// AccessGroups is the group ID allowlist for inbound peers on private services. Mutually exclusive with bearer SSO.
|
|
||||||
AccessGroups []string `json:"access_groups,omitempty" gorm:"serializer:json"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitNewRecord generates a new unique ID and resets metadata for a newly created
|
// InitNewRecord generates a new unique ID and resets metadata for a newly created
|
||||||
@@ -309,12 +299,6 @@ func (s *Service) ToAPIResponse() *api.Service {
|
|||||||
Mode: &mode,
|
Mode: &mode,
|
||||||
ListenPort: &listenPort,
|
ListenPort: &listenPort,
|
||||||
PortAutoAssigned: &s.PortAutoAssigned,
|
PortAutoAssigned: &s.PortAutoAssigned,
|
||||||
Private: &s.Private,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(s.AccessGroups) > 0 {
|
|
||||||
groups := append([]string(nil), s.AccessGroups...)
|
|
||||||
resp.AccessGroups = &groups
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.ProxyCluster != "" {
|
if s.ProxyCluster != "" {
|
||||||
@@ -324,7 +308,6 @@ func (s *Service) ToAPIResponse() *api.Service {
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToProtoMapping converts the service into the wire format the proxy consumes.
|
|
||||||
func (s *Service) ToProtoMapping(operation Operation, authToken string, oidcConfig proxy.OIDCValidationConfig) *proto.ProxyMapping {
|
func (s *Service) ToProtoMapping(operation Operation, authToken string, oidcConfig proxy.OIDCValidationConfig) *proto.ProxyMapping {
|
||||||
pathMappings := s.buildPathMappings()
|
pathMappings := s.buildPathMappings()
|
||||||
|
|
||||||
@@ -366,7 +349,6 @@ func (s *Service) ToProtoMapping(operation Operation, authToken string, oidcConf
|
|||||||
RewriteRedirects: s.RewriteRedirects,
|
RewriteRedirects: s.RewriteRedirects,
|
||||||
Mode: s.Mode,
|
Mode: s.Mode,
|
||||||
ListenPort: int32(s.ListenPort), //nolint:gosec
|
ListenPort: int32(s.ListenPort), //nolint:gosec
|
||||||
Private: s.Private,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if r := restrictionsToProto(s.Restrictions); r != nil {
|
if r := restrictionsToProto(s.Restrictions); r != nil {
|
||||||
@@ -473,8 +455,7 @@ func pathRewriteToProto(mode PathRewriteMode) proto.PathRewriteMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func targetOptionsToAPI(opts TargetOptions) *api.ServiceTargetOptions {
|
func targetOptionsToAPI(opts TargetOptions) *api.ServiceTargetOptions {
|
||||||
if !opts.SkipTLSVerify && opts.RequestTimeout == 0 && opts.SessionIdleTimeout == 0 &&
|
if !opts.SkipTLSVerify && opts.RequestTimeout == 0 && opts.SessionIdleTimeout == 0 && opts.PathRewrite == "" && len(opts.CustomHeaders) == 0 {
|
||||||
opts.PathRewrite == "" && len(opts.CustomHeaders) == 0 && !opts.DirectUpstream {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
apiOpts := &api.ServiceTargetOptions{}
|
apiOpts := &api.ServiceTargetOptions{}
|
||||||
@@ -496,22 +477,17 @@ func targetOptionsToAPI(opts TargetOptions) *api.ServiceTargetOptions {
|
|||||||
if len(opts.CustomHeaders) > 0 {
|
if len(opts.CustomHeaders) > 0 {
|
||||||
apiOpts.CustomHeaders = &opts.CustomHeaders
|
apiOpts.CustomHeaders = &opts.CustomHeaders
|
||||||
}
|
}
|
||||||
if opts.DirectUpstream {
|
|
||||||
apiOpts.DirectUpstream = &opts.DirectUpstream
|
|
||||||
}
|
|
||||||
return apiOpts
|
return apiOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
func targetOptionsToProto(opts TargetOptions) *proto.PathTargetOptions {
|
func targetOptionsToProto(opts TargetOptions) *proto.PathTargetOptions {
|
||||||
if !opts.SkipTLSVerify && opts.PathRewrite == "" && opts.RequestTimeout == 0 &&
|
if !opts.SkipTLSVerify && opts.PathRewrite == "" && opts.RequestTimeout == 0 && len(opts.CustomHeaders) == 0 {
|
||||||
len(opts.CustomHeaders) == 0 && !opts.DirectUpstream {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
popts := &proto.PathTargetOptions{
|
popts := &proto.PathTargetOptions{
|
||||||
SkipTlsVerify: opts.SkipTLSVerify,
|
SkipTlsVerify: opts.SkipTLSVerify,
|
||||||
PathRewrite: pathRewriteToProto(opts.PathRewrite),
|
PathRewrite: pathRewriteToProto(opts.PathRewrite),
|
||||||
CustomHeaders: opts.CustomHeaders,
|
CustomHeaders: opts.CustomHeaders,
|
||||||
DirectUpstream: opts.DirectUpstream,
|
|
||||||
}
|
}
|
||||||
if opts.RequestTimeout != 0 {
|
if opts.RequestTimeout != 0 {
|
||||||
popts.RequestTimeout = durationpb.New(opts.RequestTimeout)
|
popts.RequestTimeout = durationpb.New(opts.RequestTimeout)
|
||||||
@@ -561,9 +537,6 @@ func targetOptionsFromAPI(idx int, o *api.ServiceTargetOptions) (TargetOptions,
|
|||||||
if o.CustomHeaders != nil {
|
if o.CustomHeaders != nil {
|
||||||
opts.CustomHeaders = *o.CustomHeaders
|
opts.CustomHeaders = *o.CustomHeaders
|
||||||
}
|
}
|
||||||
if o.DirectUpstream != nil {
|
|
||||||
opts.DirectUpstream = *o.DirectUpstream
|
|
||||||
}
|
|
||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,14 +551,6 @@ func (s *Service) FromAPIRequest(req *api.ServiceRequest, accountID string) erro
|
|||||||
if req.ListenPort != nil {
|
if req.ListenPort != nil {
|
||||||
s.ListenPort = uint16(*req.ListenPort) //nolint:gosec
|
s.ListenPort = uint16(*req.ListenPort) //nolint:gosec
|
||||||
}
|
}
|
||||||
if req.Private != nil {
|
|
||||||
s.Private = *req.Private
|
|
||||||
}
|
|
||||||
if req.AccessGroups != nil {
|
|
||||||
s.AccessGroups = append([]string(nil), *req.AccessGroups...)
|
|
||||||
} else {
|
|
||||||
s.AccessGroups = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
targets, err := targetsFromAPI(accountID, req.Targets)
|
targets, err := targetsFromAPI(accountID, req.Targets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -775,9 +740,6 @@ func (s *Service) Validate() error {
|
|||||||
if err := validateAccessRestrictions(&s.Restrictions); err != nil {
|
if err := validateAccessRestrictions(&s.Restrictions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.validatePrivateRequirements(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch s.Mode {
|
switch s.Mode {
|
||||||
case ModeHTTP:
|
case ModeHTTP:
|
||||||
@@ -791,23 +753,6 @@ func (s *Service) Validate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validatePrivateRequirements enforces the private-service contract: HTTP mode, ≥1 access group, no bearer auth.
|
|
||||||
func (s *Service) validatePrivateRequirements() error {
|
|
||||||
if !s.Private {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if s.Mode != "" && s.Mode != ModeHTTP {
|
|
||||||
return fmt.Errorf("private services only support HTTP mode, got %q", s.Mode)
|
|
||||||
}
|
|
||||||
if len(s.AccessGroups) == 0 {
|
|
||||||
return errors.New("private services require at least one access group")
|
|
||||||
}
|
|
||||||
if s.Auth.BearerAuth != nil && s.Auth.BearerAuth.Enabled {
|
|
||||||
return errors.New("private services cannot enable bearer auth (SSO): NetBird-only access and SSO are mutually exclusive")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) validateHTTPMode() error {
|
func (s *Service) validateHTTPMode() error {
|
||||||
if s.Domain == "" {
|
if s.Domain == "" {
|
||||||
return errors.New("service domain is required")
|
return errors.New("service domain is required")
|
||||||
@@ -854,21 +799,11 @@ func (s *Service) validateHTTPTargets() error {
|
|||||||
for i, target := range s.Targets {
|
for i, target := range s.Targets {
|
||||||
switch target.TargetType {
|
switch target.TargetType {
|
||||||
case TargetTypePeer, TargetTypeHost, TargetTypeDomain:
|
case TargetTypePeer, TargetTypeHost, TargetTypeDomain:
|
||||||
// Host is normally overwritten by replaceHostByLookup with the
|
// host field will be ignored
|
||||||
// resolved peer IP / resource address; operator-supplied values
|
|
||||||
// are honored only when DirectUpstream is set. Validate the
|
|
||||||
// override here so misconfigured hosts fail fast at API time.
|
|
||||||
if err := validateDirectUpstreamHost(i, target); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case TargetTypeSubnet:
|
case TargetTypeSubnet:
|
||||||
if target.Host == "" {
|
if target.Host == "" {
|
||||||
return fmt.Errorf("target %d has empty host but target_type is %q", i, target.TargetType)
|
return fmt.Errorf("target %d has empty host but target_type is %q", i, target.TargetType)
|
||||||
}
|
}
|
||||||
case TargetTypeCluster:
|
|
||||||
if err := validateClusterTarget(i, target); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("target %d has invalid target_type %q", i, target.TargetType)
|
return fmt.Errorf("target %d has invalid target_type %q", i, target.TargetType)
|
||||||
}
|
}
|
||||||
@@ -886,67 +821,25 @@ func (s *Service) validateHTTPTargets() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateClusterTarget cluster targets should not have empty hosts and should have direct upstream enabled.
|
|
||||||
func validateClusterTarget(idx int, target *Target) error {
|
|
||||||
host := strings.TrimSpace(target.Host)
|
|
||||||
if host == "" {
|
|
||||||
return fmt.Errorf("target %d: has empty host", idx)
|
|
||||||
}
|
|
||||||
if !target.Options.DirectUpstream {
|
|
||||||
return fmt.Errorf("target %d: %s has direct upstream disabled", idx, target.Host)
|
|
||||||
}
|
|
||||||
return validateDirectUpstreamHost(idx, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateDirectUpstreamHost validates the operator-supplied Host on a
|
|
||||||
// peer/host/domain target when DirectUpstream is set. Empty Host is
|
|
||||||
// allowed — the lookup fills in the default peer IP / resource address.
|
|
||||||
// Without DirectUpstream the Host value is silently overwritten by
|
|
||||||
// replaceHostByLookup, so we don't validate it (preserves the historical
|
|
||||||
// behaviour where APIs accepted any value and dropped it). Non-empty
|
|
||||||
// Host with DirectUpstream must look like a hostname or IP and must
|
|
||||||
// not carry a port (port lives on Target.Port).
|
|
||||||
func validateDirectUpstreamHost(idx int, target *Target) error {
|
|
||||||
if !target.Options.DirectUpstream {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
host := strings.TrimSpace(target.Host)
|
|
||||||
if host == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if strings.ContainsAny(host, " \t/") {
|
|
||||||
return fmt.Errorf("target %d: host %q contains invalid characters", idx, host)
|
|
||||||
}
|
|
||||||
if _, _, err := net.SplitHostPort(host); err == nil {
|
|
||||||
return fmt.Errorf("target %d: host %q must not include a port (set target.port instead)", idx, host)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) validateL4Target(target *Target) error {
|
func (s *Service) validateL4Target(target *Target) error {
|
||||||
// L4 services have a single target; per-target disable is meaningless
|
// L4 services have a single target; per-target disable is meaningless
|
||||||
// (use the service-level Enabled flag instead). Force it on so that
|
// (use the service-level Enabled flag instead). Force it on so that
|
||||||
// buildPathMappings always includes the target in the proto.
|
// buildPathMappings always includes the target in the proto.
|
||||||
target.Enabled = true
|
target.Enabled = true
|
||||||
|
|
||||||
|
if target.Port == 0 {
|
||||||
|
return errors.New("target port is required for L4 services")
|
||||||
|
}
|
||||||
if target.TargetId == "" {
|
if target.TargetId == "" {
|
||||||
return errors.New("target_id is required for L4 services")
|
return errors.New("target_id is required for L4 services")
|
||||||
}
|
}
|
||||||
if target.TargetType != TargetTypeCluster && target.Port == 0 {
|
|
||||||
return errors.New("target port is required for L4 services")
|
|
||||||
}
|
|
||||||
switch target.TargetType {
|
switch target.TargetType {
|
||||||
case TargetTypePeer, TargetTypeHost, TargetTypeDomain:
|
case TargetTypePeer, TargetTypeHost, TargetTypeDomain:
|
||||||
if err := validateDirectUpstreamHost(0, target); err != nil {
|
// OK
|
||||||
return err
|
|
||||||
}
|
|
||||||
case TargetTypeSubnet:
|
case TargetTypeSubnet:
|
||||||
if target.Host == "" {
|
if target.Host == "" {
|
||||||
return errors.New("target host is required for subnet targets")
|
return errors.New("target host is required for subnet targets")
|
||||||
}
|
}
|
||||||
case TargetTypeCluster:
|
|
||||||
// target_id carries the cluster address; the proxy resolves
|
|
||||||
// the upstream at request time.
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid target_type %q for L4 service", target.TargetType)
|
return fmt.Errorf("invalid target_type %q for L4 service", target.TargetType)
|
||||||
}
|
}
|
||||||
@@ -1281,11 +1174,6 @@ func (s *Service) Copy() *Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var accessGroups []string
|
|
||||||
if len(s.AccessGroups) > 0 {
|
|
||||||
accessGroups = append([]string(nil), s.AccessGroups...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
ID: s.ID,
|
ID: s.ID,
|
||||||
AccountID: s.AccountID,
|
AccountID: s.AccountID,
|
||||||
@@ -1307,8 +1195,6 @@ func (s *Service) Copy() *Service {
|
|||||||
Mode: s.Mode,
|
Mode: s.Mode,
|
||||||
ListenPort: s.ListenPort,
|
ListenPort: s.ListenPort,
|
||||||
PortAutoAssigned: s.PortAutoAssigned,
|
PortAutoAssigned: s.PortAutoAssigned,
|
||||||
Private: s.Private,
|
|
||||||
AccessGroups: accessGroups,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/proxy"
|
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/proxy"
|
||||||
"github.com/netbirdio/netbird/shared/hash/argon2id"
|
"github.com/netbirdio/netbird/shared/hash/argon2id"
|
||||||
"github.com/netbirdio/netbird/shared/management/http/api"
|
|
||||||
"github.com/netbirdio/netbird/shared/management/proto"
|
"github.com/netbirdio/netbird/shared/management/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1117,191 +1116,3 @@ func TestValidate_HeaderAuths(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "exceeds maximum length")
|
assert.Contains(t, err.Error(), "exceeds maximum length")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidate_HTTPClusterTarget(t *testing.T) {
|
|
||||||
rp := validProxy()
|
|
||||||
rp.Targets = []*Target{{
|
|
||||||
TargetId: "eu.proxy.netbird.io",
|
|
||||||
TargetType: TargetTypeCluster,
|
|
||||||
Protocol: "http",
|
|
||||||
Host: "backend.lan",
|
|
||||||
Options: TargetOptions{DirectUpstream: true},
|
|
||||||
Enabled: true,
|
|
||||||
}}
|
|
||||||
require.NoError(t, rp.Validate(), "HTTP cluster target with target_id, host, and direct_upstream must validate")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidate_HTTPClusterTarget_RequiresTargetId(t *testing.T) {
|
|
||||||
rp := validProxy()
|
|
||||||
rp.Targets = []*Target{{
|
|
||||||
TargetType: TargetTypeCluster,
|
|
||||||
Protocol: "http",
|
|
||||||
Host: "backend.lan",
|
|
||||||
Options: TargetOptions{DirectUpstream: true},
|
|
||||||
Enabled: true,
|
|
||||||
}}
|
|
||||||
assert.ErrorContains(t, rp.Validate(), "empty target_id", "cluster target must reject empty target_id")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestValidate_HTTPClusterTarget_RequiresHost pins the new cluster-target
|
|
||||||
// rule that operator-supplied Host is mandatory: cluster targets dial the
|
|
||||||
// upstream via the host network stack (direct_upstream is implied), so an
|
|
||||||
// empty Host leaves the proxy with nothing to dial.
|
|
||||||
func TestValidate_HTTPClusterTarget_RequiresHost(t *testing.T) {
|
|
||||||
rp := validProxy()
|
|
||||||
rp.Targets = []*Target{{
|
|
||||||
TargetId: "eu.proxy.netbird.io",
|
|
||||||
TargetType: TargetTypeCluster,
|
|
||||||
Protocol: "http",
|
|
||||||
Options: TargetOptions{DirectUpstream: true},
|
|
||||||
Enabled: true,
|
|
||||||
}}
|
|
||||||
assert.ErrorContains(t, rp.Validate(), "empty host", "cluster target must reject empty host")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestValidate_HTTPClusterTarget_RequiresDirectUpstream pins the second
|
|
||||||
// half of the cluster-target rule: DirectUpstream must be true so the
|
|
||||||
// stdlib transport branch in MultiTransport is taken. Without it the
|
|
||||||
// embedded NetBird client would try to dial the cluster address through
|
|
||||||
// the WG tunnel, which is the wrong network for a cluster upstream.
|
|
||||||
func TestValidate_HTTPClusterTarget_RequiresDirectUpstream(t *testing.T) {
|
|
||||||
rp := validProxy()
|
|
||||||
rp.Targets = []*Target{{
|
|
||||||
TargetId: "eu.proxy.netbird.io",
|
|
||||||
TargetType: TargetTypeCluster,
|
|
||||||
Protocol: "http",
|
|
||||||
Host: "backend.lan",
|
|
||||||
Enabled: true,
|
|
||||||
}}
|
|
||||||
assert.ErrorContains(t, rp.Validate(), "direct upstream disabled", "cluster target must reject direct_upstream=false")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidate_L4ClusterTarget(t *testing.T) {
|
|
||||||
rp := validProxy()
|
|
||||||
rp.Mode = ModeTCP
|
|
||||||
rp.ListenPort = 9000
|
|
||||||
rp.Targets = []*Target{{
|
|
||||||
TargetId: "eu.proxy.netbird.io",
|
|
||||||
TargetType: TargetTypeCluster,
|
|
||||||
Protocol: "tcp",
|
|
||||||
Enabled: true,
|
|
||||||
}}
|
|
||||||
require.NoError(t, rp.Validate(), "L4 cluster target must validate without an explicit port")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestService_Copy_RoundtripsPrivate(t *testing.T) {
|
|
||||||
svc := validProxy()
|
|
||||||
svc.Private = true
|
|
||||||
svc.AccessGroups = []string{"grp-admins", "grp-ops"}
|
|
||||||
cp := svc.Copy()
|
|
||||||
require.NotNil(t, cp)
|
|
||||||
assert.True(t, cp.Private)
|
|
||||||
assert.Equal(t, []string{"grp-admins", "grp-ops"}, cp.AccessGroups)
|
|
||||||
|
|
||||||
cp.Private = false
|
|
||||||
assert.True(t, svc.Private)
|
|
||||||
|
|
||||||
cp.AccessGroups[0] = "grp-other"
|
|
||||||
assert.Equal(t, []string{"grp-admins", "grp-ops"}, svc.AccessGroups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestService_APIRoundtrip_Private(t *testing.T) {
|
|
||||||
enabled := true
|
|
||||||
private := true
|
|
||||||
accessGroups := []string{"grp-admins"}
|
|
||||||
targets := []api.ServiceTarget{{
|
|
||||||
TargetId: "eu.proxy.netbird.io",
|
|
||||||
TargetType: api.ServiceTargetTargetType("cluster"),
|
|
||||||
Protocol: "http",
|
|
||||||
Port: 80,
|
|
||||||
Enabled: true,
|
|
||||||
}}
|
|
||||||
req := &api.ServiceRequest{
|
|
||||||
Name: "svc-private",
|
|
||||||
Domain: "myapp.eu.proxy.netbird.io",
|
|
||||||
Enabled: enabled,
|
|
||||||
Private: &private,
|
|
||||||
AccessGroups: &accessGroups,
|
|
||||||
Targets: &targets,
|
|
||||||
}
|
|
||||||
|
|
||||||
svc := &Service{}
|
|
||||||
require.NoError(t, svc.FromAPIRequest(req, "acc-1"))
|
|
||||||
assert.True(t, svc.Private)
|
|
||||||
assert.Equal(t, []string{"grp-admins"}, svc.AccessGroups)
|
|
||||||
|
|
||||||
resp := svc.ToAPIResponse()
|
|
||||||
require.NotNil(t, resp.Private)
|
|
||||||
assert.True(t, *resp.Private)
|
|
||||||
require.NotNil(t, resp.AccessGroups)
|
|
||||||
assert.Equal(t, []string{"grp-admins"}, *resp.AccessGroups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidate_Private_RequiresAccessGroups(t *testing.T) {
|
|
||||||
rp := validProxy()
|
|
||||||
rp.Private = true
|
|
||||||
rp.Targets = []*Target{{
|
|
||||||
TargetId: "eu.proxy.netbird.io",
|
|
||||||
TargetType: TargetTypeCluster,
|
|
||||||
Protocol: "http",
|
|
||||||
Host: "backend.lan",
|
|
||||||
Options: TargetOptions{DirectUpstream: true},
|
|
||||||
Enabled: true,
|
|
||||||
}}
|
|
||||||
assert.ErrorContains(t, rp.Validate(), "access group")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidate_Private_RejectsBearerAuth(t *testing.T) {
|
|
||||||
rp := validProxy()
|
|
||||||
rp.Private = true
|
|
||||||
rp.AccessGroups = []string{"grp-admins"}
|
|
||||||
rp.Auth.BearerAuth = &BearerAuthConfig{
|
|
||||||
Enabled: true,
|
|
||||||
DistributionGroups: []string{"grp-sso"},
|
|
||||||
}
|
|
||||||
rp.Targets = []*Target{{
|
|
||||||
TargetId: "eu.proxy.netbird.io",
|
|
||||||
TargetType: TargetTypeCluster,
|
|
||||||
Protocol: "http",
|
|
||||||
Host: "backend.lan",
|
|
||||||
Options: TargetOptions{DirectUpstream: true},
|
|
||||||
Enabled: true,
|
|
||||||
}}
|
|
||||||
assert.ErrorContains(t, rp.Validate(), "mutually exclusive")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidate_Private_AcceptsNonClusterTargets(t *testing.T) {
|
|
||||||
rp := validProxy()
|
|
||||||
rp.Private = true
|
|
||||||
rp.AccessGroups = []string{"grp-admins"}
|
|
||||||
require.NoError(t, rp.Validate())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidate_Private_AcceptsClusterTargetWithAccessGroups(t *testing.T) {
|
|
||||||
rp := validProxy()
|
|
||||||
rp.Private = true
|
|
||||||
rp.AccessGroups = []string{"grp-admins"}
|
|
||||||
rp.Targets = []*Target{{
|
|
||||||
TargetId: "eu.proxy.netbird.io",
|
|
||||||
TargetType: TargetTypeCluster,
|
|
||||||
Protocol: "http",
|
|
||||||
Host: "backend.lan",
|
|
||||||
Options: TargetOptions{DirectUpstream: true},
|
|
||||||
Enabled: true,
|
|
||||||
}}
|
|
||||||
require.NoError(t, rp.Validate())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidate_Private_RejectsNonHTTPMode(t *testing.T) {
|
|
||||||
rp := validProxy()
|
|
||||||
rp.Private = true
|
|
||||||
rp.AccessGroups = []string{"grp-admins"}
|
|
||||||
rp.Mode = ModeTCP
|
|
||||||
rp.Targets = []*Target{{
|
|
||||||
TargetId: "eu.proxy.netbird.io",
|
|
||||||
TargetType: TargetTypeCluster,
|
|
||||||
Protocol: "tcp",
|
|
||||||
Enabled: true,
|
|
||||||
}}
|
|
||||||
assert.ErrorContains(t, rp.Validate(), "HTTP")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,20 +20,6 @@ type KeyPair struct {
|
|||||||
type Claims struct {
|
type Claims struct {
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
Method auth.Method `json:"method"`
|
Method auth.Method `json:"method"`
|
||||||
// Email is the calling user's email address. Carried so the
|
|
||||||
// proxy can stamp identity on upstream requests (e.g.
|
|
||||||
// x-litellm-end-user-id) without an extra management
|
|
||||||
// round-trip on every cookie-bearing request.
|
|
||||||
Email string `json:"email,omitempty"`
|
|
||||||
// Groups carries the user's group IDs so the proxy can stamp them
|
|
||||||
// onto upstream requests (X-NetBird-Groups) from the cookie path
|
|
||||||
// without an extra management round-trip.
|
|
||||||
Groups []string `json:"groups,omitempty"`
|
|
||||||
// GroupNames carries the human-readable display names for the ids
|
|
||||||
// in Groups, ordered identically (positional pairing). Slice may be
|
|
||||||
// shorter than Groups for tokens minted before names were
|
|
||||||
// resolvable; the consumer falls back to ids for missing positions.
|
|
||||||
GroupNames []string `json:"group_names,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateKeyPair() (*KeyPair, error) {
|
func GenerateKeyPair() (*KeyPair, error) {
|
||||||
@@ -48,13 +34,7 @@ func GenerateKeyPair() (*KeyPair, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignToken mints a session JWT for the given user and domain. email,
|
func SignToken(privKeyB64, userID, domain string, method auth.Method, expiration time.Duration) (string, error) {
|
||||||
// groups, and groupNames, when non-empty, are embedded so the proxy can
|
|
||||||
// authorise and stamp identity for policy-aware middlewares without a
|
|
||||||
// management round-trip on every cookie-bearing request. groupNames
|
|
||||||
// pairs positionally with groups; pass nil when names couldn't be
|
|
||||||
// resolved.
|
|
||||||
func SignToken(privKeyB64, userID, email, domain string, method auth.Method, groups, groupNames []string, expiration time.Duration) (string, error) {
|
|
||||||
privKeyBytes, err := base64.StdEncoding.DecodeString(privKeyB64)
|
privKeyBytes, err := base64.StdEncoding.DecodeString(privKeyB64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("decode private key: %w", err)
|
return "", fmt.Errorf("decode private key: %w", err)
|
||||||
@@ -76,10 +56,7 @@ func SignToken(privKeyB64, userID, email, domain string, method auth.Method, gro
|
|||||||
IssuedAt: jwt.NewNumericDate(now),
|
IssuedAt: jwt.NewNumericDate(now),
|
||||||
NotBefore: jwt.NewNumericDate(now),
|
NotBefore: jwt.NewNumericDate(now),
|
||||||
},
|
},
|
||||||
Method: method,
|
Method: method,
|
||||||
Email: email,
|
|
||||||
Groups: append([]string(nil), groups...),
|
|
||||||
GroupNames: append([]string(nil), groupNames...),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
|
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func NewManager(store store.Store, accountManager account.Manager, permissionsMa
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerImpl) GetAllZones(ctx context.Context, accountID, userID string) ([]*zones.Zone, error) {
|
func (m *managerImpl) GetAllZones(ctx context.Context, accountID, userID string) ([]*zones.Zone, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Read)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ func (m *managerImpl) GetAllZones(ctx context.Context, accountID, userID string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerImpl) GetZone(ctx context.Context, accountID, userID, zoneID string) (*zones.Zone, error) {
|
func (m *managerImpl) GetZone(ctx context.Context, accountID, userID, zoneID string) (*zones.Zone, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Read)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ func (m *managerImpl) GetZone(ctx context.Context, accountID, userID, zoneID str
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerImpl) CreateZone(ctx context.Context, accountID, userID string, zone *zones.Zone) (*zones.Zone, error) {
|
func (m *managerImpl) CreateZone(ctx context.Context, accountID, userID string, zone *zones.Zone) (*zones.Zone, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Create)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Create)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ func (m *managerImpl) CreateZone(ctx context.Context, accountID, userID string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerImpl) UpdateZone(ctx context.Context, accountID, userID string, updatedZone *zones.Zone) (*zones.Zone, error) {
|
func (m *managerImpl) UpdateZone(ctx context.Context, accountID, userID string, updatedZone *zones.Zone) (*zones.Zone, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Update)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ func (m *managerImpl) UpdateZone(ctx context.Context, accountID, userID string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerImpl) DeleteZone(ctx context.Context, accountID, userID, zoneID string) error {
|
func (m *managerImpl) DeleteZone(ctx context.Context, accountID, userID, zoneID string) error {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Delete)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Delete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.NewPermissionValidationError(err)
|
return status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func TestManagerImpl_GetAllZones(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.GetAllZones(ctx, testAccountID, testUserID)
|
result, err := manager.GetAllZones(ctx, testAccountID, testUserID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -95,7 +95,7 @@ func TestManagerImpl_GetAllZones(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
||||||
Return(false, ctx, nil)
|
Return(false, nil)
|
||||||
|
|
||||||
result, err := manager.GetAllZones(ctx, testAccountID, testUserID)
|
result, err := manager.GetAllZones(ctx, testAccountID, testUserID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -112,7 +112,7 @@ func TestManagerImpl_GetAllZones(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
||||||
Return(false, ctx, status.Errorf(status.Internal, "permission check failed"))
|
Return(false, status.Errorf(status.Internal, "permission check failed"))
|
||||||
|
|
||||||
result, err := manager.GetAllZones(ctx, testAccountID, testUserID)
|
result, err := manager.GetAllZones(ctx, testAccountID, testUserID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -134,7 +134,7 @@ func TestManagerImpl_GetZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.GetZone(ctx, testAccountID, testUserID, zone.ID)
|
result, err := manager.GetZone(ctx, testAccountID, testUserID, zone.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -150,7 +150,7 @@ func TestManagerImpl_GetZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
||||||
Return(false, ctx, nil)
|
Return(false, nil)
|
||||||
|
|
||||||
result, err := manager.GetZone(ctx, testAccountID, testUserID, testZoneID)
|
result, err := manager.GetZone(ctx, testAccountID, testUserID, testZoneID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -179,7 +179,7 @@ func TestManagerImpl_CreateZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||||
assert.Equal(t, testUserID, initiatorID)
|
assert.Equal(t, testUserID, initiatorID)
|
||||||
@@ -212,7 +212,7 @@ func TestManagerImpl_CreateZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(false, ctx, nil)
|
Return(false, nil)
|
||||||
|
|
||||||
result, err := manager.CreateZone(ctx, testAccountID, testUserID, inputZone)
|
result, err := manager.CreateZone(ctx, testAccountID, testUserID, inputZone)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -235,7 +235,7 @@ func TestManagerImpl_CreateZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.CreateZone(ctx, testAccountID, testUserID, inputZone)
|
result, err := manager.CreateZone(ctx, testAccountID, testUserID, inputZone)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -261,7 +261,7 @@ func TestManagerImpl_CreateZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.CreateZone(ctx, testAccountID, testUserID, inputZone)
|
result, err := manager.CreateZone(ctx, testAccountID, testUserID, inputZone)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -293,7 +293,7 @@ func TestManagerImpl_CreateZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.CreateZone(ctx, testAccountID, testUserID, inputZone)
|
result, err := manager.CreateZone(ctx, testAccountID, testUserID, inputZone)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -319,7 +319,7 @@ func TestManagerImpl_CreateZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.CreateZone(ctx, testAccountID, testUserID, inputZone)
|
result, err := manager.CreateZone(ctx, testAccountID, testUserID, inputZone)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -354,7 +354,7 @@ func TestManagerImpl_UpdateZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
storeEventCalled := false
|
storeEventCalled := false
|
||||||
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||||
@@ -394,7 +394,7 @@ func TestManagerImpl_UpdateZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.UpdateZone(ctx, testAccountID, testUserID, updatedZone)
|
result, err := manager.UpdateZone(ctx, testAccountID, testUserID, updatedZone)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -418,7 +418,7 @@ func TestManagerImpl_UpdateZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
||||||
Return(false, ctx, nil)
|
Return(false, nil)
|
||||||
|
|
||||||
result, err := manager.UpdateZone(ctx, testAccountID, testUserID, updatedZone)
|
result, err := manager.UpdateZone(ctx, testAccountID, testUserID, updatedZone)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -441,7 +441,7 @@ func TestManagerImpl_UpdateZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.UpdateZone(ctx, testAccountID, testUserID, updatedZone)
|
result, err := manager.UpdateZone(ctx, testAccountID, testUserID, updatedZone)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -471,7 +471,7 @@ func TestManagerImpl_DeleteZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
storeEventCallCount := 0
|
storeEventCallCount := 0
|
||||||
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||||
@@ -503,7 +503,7 @@ func TestManagerImpl_DeleteZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
storeEventCalled := false
|
storeEventCalled := false
|
||||||
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||||
@@ -529,7 +529,7 @@ func TestManagerImpl_DeleteZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
||||||
Return(false, ctx, nil)
|
Return(false, nil)
|
||||||
|
|
||||||
err := manager.DeleteZone(ctx, testAccountID, testUserID, testZoneID)
|
err := manager.DeleteZone(ctx, testAccountID, testUserID, testZoneID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -545,7 +545,7 @@ func TestManagerImpl_DeleteZone(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
err := manager.DeleteZone(ctx, testAccountID, testUserID, "non-existent-zone")
|
err := manager.DeleteZone(ctx, testAccountID, testUserID, "non-existent-zone")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func NewManager(store store.Store, accountManager account.Manager, permissionsMa
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerImpl) GetAllRecords(ctx context.Context, accountID, userID, zoneID string) ([]*records.Record, error) {
|
func (m *managerImpl) GetAllRecords(ctx context.Context, accountID, userID, zoneID string) ([]*records.Record, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Read)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ func (m *managerImpl) GetAllRecords(ctx context.Context, accountID, userID, zone
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerImpl) GetRecord(ctx context.Context, accountID, userID, zoneID, recordID string) (*records.Record, error) {
|
func (m *managerImpl) GetRecord(ctx context.Context, accountID, userID, zoneID, recordID string) (*records.Record, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Read)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ func (m *managerImpl) GetRecord(ctx context.Context, accountID, userID, zoneID,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerImpl) CreateRecord(ctx context.Context, accountID, userID, zoneID string, record *records.Record) (*records.Record, error) {
|
func (m *managerImpl) CreateRecord(ctx context.Context, accountID, userID, zoneID string, record *records.Record) (*records.Record, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Create)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Create)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@ func (m *managerImpl) CreateRecord(ctx context.Context, accountID, userID, zoneI
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerImpl) UpdateRecord(ctx context.Context, accountID, userID, zoneID string, updatedRecord *records.Record) (*records.Record, error) {
|
func (m *managerImpl) UpdateRecord(ctx context.Context, accountID, userID, zoneID string, updatedRecord *records.Record) (*records.Record, error) {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Update)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ func (m *managerImpl) UpdateRecord(ctx context.Context, accountID, userID, zoneI
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *managerImpl) DeleteRecord(ctx context.Context, accountID, userID, zoneID, recordID string) error {
|
func (m *managerImpl) DeleteRecord(ctx context.Context, accountID, userID, zoneID, recordID string) error {
|
||||||
ok, ctx, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Delete)
|
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Delete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.NewPermissionValidationError(err)
|
return status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func TestManagerImpl_GetAllRecords(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.GetAllRecords(ctx, testAccountID, testUserID, zone.ID)
|
result, err := manager.GetAllRecords(ctx, testAccountID, testUserID, zone.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -96,7 +96,7 @@ func TestManagerImpl_GetAllRecords(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
||||||
Return(false, ctx, nil)
|
Return(false, nil)
|
||||||
|
|
||||||
result, err := manager.GetAllRecords(ctx, testAccountID, testUserID, zone.ID)
|
result, err := manager.GetAllRecords(ctx, testAccountID, testUserID, zone.ID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -113,7 +113,7 @@ func TestManagerImpl_GetAllRecords(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
||||||
Return(false, ctx, status.Errorf(status.Internal, "permission check failed"))
|
Return(false, status.Errorf(status.Internal, "permission check failed"))
|
||||||
|
|
||||||
result, err := manager.GetAllRecords(ctx, testAccountID, testUserID, zone.ID)
|
result, err := manager.GetAllRecords(ctx, testAccountID, testUserID, zone.ID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -135,7 +135,7 @@ func TestManagerImpl_GetRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.GetRecord(ctx, testAccountID, testUserID, zone.ID, record.ID)
|
result, err := manager.GetRecord(ctx, testAccountID, testUserID, zone.ID, record.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -153,7 +153,7 @@ func TestManagerImpl_GetRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Read).
|
||||||
Return(false, ctx, nil)
|
Return(false, nil)
|
||||||
|
|
||||||
result, err := manager.GetRecord(ctx, testAccountID, testUserID, zone.ID, testRecordID)
|
result, err := manager.GetRecord(ctx, testAccountID, testUserID, zone.ID, testRecordID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -181,7 +181,7 @@ func TestManagerImpl_CreateRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||||
assert.Equal(t, testUserID, initiatorID)
|
assert.Equal(t, testUserID, initiatorID)
|
||||||
@@ -215,7 +215,7 @@ func TestManagerImpl_CreateRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||||
assert.Equal(t, testUserID, initiatorID)
|
assert.Equal(t, testUserID, initiatorID)
|
||||||
@@ -244,7 +244,7 @@ func TestManagerImpl_CreateRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||||
assert.Equal(t, testUserID, initiatorID)
|
assert.Equal(t, testUserID, initiatorID)
|
||||||
@@ -273,7 +273,7 @@ func TestManagerImpl_CreateRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(false, ctx, nil)
|
Return(false, nil)
|
||||||
|
|
||||||
result, err := manager.CreateRecord(ctx, testAccountID, testUserID, zone.ID, inputRecord)
|
result, err := manager.CreateRecord(ctx, testAccountID, testUserID, zone.ID, inputRecord)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -297,7 +297,7 @@ func TestManagerImpl_CreateRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.CreateRecord(ctx, testAccountID, testUserID, zone.ID, inputRecord)
|
result, err := manager.CreateRecord(ctx, testAccountID, testUserID, zone.ID, inputRecord)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -323,7 +323,7 @@ func TestManagerImpl_CreateRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.CreateRecord(ctx, testAccountID, testUserID, zone.ID, inputRecord)
|
result, err := manager.CreateRecord(ctx, testAccountID, testUserID, zone.ID, inputRecord)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -349,7 +349,7 @@ func TestManagerImpl_CreateRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Create).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.CreateRecord(ctx, testAccountID, testUserID, zone.ID, inputRecord)
|
result, err := manager.CreateRecord(ctx, testAccountID, testUserID, zone.ID, inputRecord)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -380,7 +380,7 @@ func TestManagerImpl_UpdateRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
storeEventCalled := false
|
storeEventCalled := false
|
||||||
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||||
@@ -418,7 +418,7 @@ func TestManagerImpl_UpdateRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||||
// Event should be stored
|
// Event should be stored
|
||||||
@@ -445,7 +445,7 @@ func TestManagerImpl_UpdateRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
||||||
Return(false, ctx, nil)
|
Return(false, nil)
|
||||||
|
|
||||||
result, err := manager.UpdateRecord(ctx, testAccountID, testUserID, zone.ID, updatedRecord)
|
result, err := manager.UpdateRecord(ctx, testAccountID, testUserID, zone.ID, updatedRecord)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -470,7 +470,7 @@ func TestManagerImpl_UpdateRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.UpdateRecord(ctx, testAccountID, testUserID, zone.ID, updatedRecord)
|
result, err := manager.UpdateRecord(ctx, testAccountID, testUserID, zone.ID, updatedRecord)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -500,7 +500,7 @@ func TestManagerImpl_UpdateRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Update).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
result, err := manager.UpdateRecord(ctx, testAccountID, testUserID, zone.ID, updatedRecord)
|
result, err := manager.UpdateRecord(ctx, testAccountID, testUserID, zone.ID, updatedRecord)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -523,7 +523,7 @@ func TestManagerImpl_DeleteRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
storeEventCalled := false
|
storeEventCalled := false
|
||||||
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
mockAccountManager.StoreEventFunc = func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||||
@@ -549,7 +549,7 @@ func TestManagerImpl_DeleteRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
||||||
Return(false, ctx, nil)
|
Return(false, nil)
|
||||||
|
|
||||||
err := manager.DeleteRecord(ctx, testAccountID, testUserID, zone.ID, testRecordID)
|
err := manager.DeleteRecord(ctx, testAccountID, testUserID, zone.ID, testRecordID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -565,7 +565,7 @@ func TestManagerImpl_DeleteRecord(t *testing.T) {
|
|||||||
|
|
||||||
mockPermissionsManager.EXPECT().
|
mockPermissionsManager.EXPECT().
|
||||||
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
ValidateUserPermissions(ctx, testAccountID, testUserID, modules.Dns, operations.Delete).
|
||||||
Return(true, ctx, nil)
|
Return(true, nil)
|
||||||
|
|
||||||
err := manager.DeleteRecord(ctx, testAccountID, testUserID, zone.ID, "non-existent-record")
|
err := manager.DeleteRecord(ctx, testAccountID, testUserID, zone.ID, "non-existent-record")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware/v2"
|
grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware/v2"
|
||||||
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip"
|
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip"
|
||||||
"github.com/rs/cors"
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@@ -21,6 +19,7 @@ import (
|
|||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
cachestore "github.com/eko/gocache/lib/v4/store"
|
cachestore "github.com/eko/gocache/lib/v4/store"
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
"github.com/netbirdio/netbird/formatter/hook"
|
"github.com/netbirdio/netbird/formatter/hook"
|
||||||
@@ -28,20 +27,16 @@ import (
|
|||||||
accesslogsmanager "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/accesslogs/manager"
|
accesslogsmanager "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/accesslogs/manager"
|
||||||
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
activitystore "github.com/netbirdio/netbird/management/server/activity/store"
|
|
||||||
nbcache "github.com/netbirdio/netbird/management/server/cache"
|
nbcache "github.com/netbirdio/netbird/management/server/cache"
|
||||||
nbContext "github.com/netbirdio/netbird/management/server/context"
|
nbContext "github.com/netbirdio/netbird/management/server/context"
|
||||||
nbhttp "github.com/netbirdio/netbird/management/server/http"
|
nbhttp "github.com/netbirdio/netbird/management/server/http"
|
||||||
"github.com/netbirdio/netbird/management/server/http/middleware"
|
"github.com/netbirdio/netbird/management/server/http/middleware"
|
||||||
"github.com/netbirdio/netbird/management/server/idp"
|
|
||||||
"github.com/netbirdio/netbird/management/server/store"
|
"github.com/netbirdio/netbird/management/server/store"
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
"github.com/netbirdio/netbird/util/crypt"
|
"github.com/netbirdio/netbird/util/crypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const apiPrefix = "/api"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
kaep = keepalive.EnforcementPolicy{
|
kaep = keepalive.EnforcementPolicy{
|
||||||
MinTime: 15 * time.Second,
|
MinTime: 15 * time.Second,
|
||||||
@@ -99,17 +94,12 @@ func (s *BaseServer) Store() store.Store {
|
|||||||
|
|
||||||
func (s *BaseServer) EventStore() activity.Store {
|
func (s *BaseServer) EventStore() activity.Store {
|
||||||
return Create(s, func() activity.Store {
|
return Create(s, func() activity.Store {
|
||||||
var err error
|
integrationMetrics, err := integrations.InitIntegrationMetrics(context.Background(), s.Metrics())
|
||||||
key := s.Config.DataStoreEncryptionKey
|
if err != nil {
|
||||||
if key == "" {
|
log.Fatalf("failed to initialize integration metrics: %v", err)
|
||||||
log.Debugf("generate new activity store encryption key")
|
|
||||||
key, err = crypt.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to generate event store encryption key: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
eventStore, err := activitystore.NewSqlStore(context.Background(), s.Config.Datadir, key)
|
eventStore, _, err := integrations.InitEventStore(context.Background(), s.Config.Datadir, s.Config.DataStoreEncryptionKey, integrationMetrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to initialize event store: %v", err)
|
log.Fatalf("failed to initialize event store: %v", err)
|
||||||
}
|
}
|
||||||
@@ -120,7 +110,7 @@ func (s *BaseServer) EventStore() activity.Store {
|
|||||||
|
|
||||||
func (s *BaseServer) APIHandler() http.Handler {
|
func (s *BaseServer) APIHandler() http.Handler {
|
||||||
return Create(s, func() http.Handler {
|
return Create(s, func() http.Handler {
|
||||||
httpAPIHandler, err := nbhttp.NewAPIHandler(context.Background(), s.Router(), s.AccountManager(), s.NetworksManager(), s.ResourcesManager(), s.RoutesManager(), s.GroupsManager(), s.GeoLocationManager(), s.AuthManager(), s.Metrics(), s.PermissionsManager(), s.SettingsManager(), s.ZonesManager(), s.RecordsManager(), s.NetworkMapController(), s.IdpManager(), s.ServiceManager(), s.ReverseProxyDomainManager(), s.AccessLogsManager(), s.ReverseProxyGRPCServer(), s.Config.ReverseProxy.TrustedHTTPProxies, s.RateLimiter(), s.IsValidChildAccount)
|
httpAPIHandler, err := nbhttp.NewAPIHandler(context.Background(), s.AccountManager(), s.NetworksManager(), s.ResourcesManager(), s.RoutesManager(), s.GroupsManager(), s.GeoLocationManager(), s.AuthManager(), s.Metrics(), s.IntegratedValidator(), s.ProxyController(), s.PermissionsManager(), s.PeersManager(), s.SettingsManager(), s.ZonesManager(), s.RecordsManager(), s.NetworkMapController(), s.IdpManager(), s.ServiceManager(), s.ReverseProxyDomainManager(), s.AccessLogsManager(), s.ReverseProxyGRPCServer(), s.Config.ReverseProxy.TrustedHTTPProxies, s.RateLimiter())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to create API handler: %v", err)
|
log.Fatalf("failed to create API handler: %v", err)
|
||||||
}
|
}
|
||||||
@@ -128,22 +118,6 @@ func (s *BaseServer) APIHandler() http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDPHandler returns the HTTP handler for the embedded IdP (Dex), or nil if
|
|
||||||
// the deployment isn't using the embedded variant.
|
|
||||||
func (s *BaseServer) IDPHandler() http.Handler {
|
|
||||||
embeddedIdP, ok := s.IdpManager().(*idp.EmbeddedIdPManager)
|
|
||||||
if !ok || embeddedIdP == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return cors.AllowAll().Handler(embeddedIdP.Handler())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BaseServer) Router() *mux.Router {
|
|
||||||
return Create(s, func() *mux.Router {
|
|
||||||
return mux.NewRouter().PathPrefix(apiPrefix).Subrouter()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BaseServer) RateLimiter() *middleware.APIRateLimiter {
|
func (s *BaseServer) RateLimiter() *middleware.APIRateLimiter {
|
||||||
return Create(s, func() *middleware.APIRateLimiter {
|
return Create(s, func() *middleware.APIRateLimiter {
|
||||||
cfg, enabled := middleware.RateLimiterConfigFromEnv()
|
cfg, enabled := middleware.RateLimiterConfigFromEnv()
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/auth"
|
"github.com/netbirdio/netbird/management/server/auth"
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator/validator"
|
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||||
"github.com/netbirdio/netbird/management/server/job"
|
"github.com/netbirdio/netbird/management/server/job"
|
||||||
nbjwt "github.com/netbirdio/netbird/shared/auth/jwt"
|
nbjwt "github.com/netbirdio/netbird/shared/auth/jwt"
|
||||||
@@ -39,7 +38,7 @@ func (s *BaseServer) JobManager() *job.Manager {
|
|||||||
|
|
||||||
func (s *BaseServer) IntegratedValidator() integrated_validator.IntegratedValidator {
|
func (s *BaseServer) IntegratedValidator() integrated_validator.IntegratedValidator {
|
||||||
return Create(s, func() integrated_validator.IntegratedValidator {
|
return Create(s, func() integrated_validator.IntegratedValidator {
|
||||||
integratedPeerValidator, err := validator.NewIntegratedValidator(
|
integratedPeerValidator, err := integrations.NewIntegratedValidator(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
s.PeersManager(),
|
s.PeersManager(),
|
||||||
s.SettingsManager(),
|
s.SettingsManager(),
|
||||||
|
|||||||
@@ -57,7 +57,13 @@ func (s *BaseServer) GeoLocationManager() geolocation.Geolocation {
|
|||||||
|
|
||||||
func (s *BaseServer) PermissionsManager() permissions.Manager {
|
func (s *BaseServer) PermissionsManager() permissions.Manager {
|
||||||
return Create(s, func() permissions.Manager {
|
return Create(s, func() permissions.Manager {
|
||||||
return permissions.NewManager(s.Store())
|
manager := integrations.InitPermissionsManager(s.Store(), s.Metrics().GetMeter())
|
||||||
|
|
||||||
|
s.AfterInit(func(s *BaseServer) {
|
||||||
|
manager.SetAccountManager(s.AccountManager())
|
||||||
|
})
|
||||||
|
|
||||||
|
return manager
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +153,7 @@ func (s *BaseServer) IdpManager() idp.Manager {
|
|||||||
return idpManager
|
return idpManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -228,7 +235,3 @@ func (s *BaseServer) ReverseProxyDomainManager() *manager.Manager {
|
|||||||
return &m
|
return &m
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BaseServer) IsValidChildAccount(_ context.Context, _, _, _ string) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ const (
|
|||||||
ManagementLegacyPort = 33073
|
ManagementLegacyPort = 33073
|
||||||
// DefaultSelfHostedDomain is the default domain used for self-hosted fresh installs.
|
// DefaultSelfHostedDomain is the default domain used for self-hosted fresh installs.
|
||||||
DefaultSelfHostedDomain = "netbird.selfhosted"
|
DefaultSelfHostedDomain = "netbird.selfhosted"
|
||||||
|
|
||||||
ContainerKeyBaseServer = "baseServer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server interface {
|
type Server interface {
|
||||||
@@ -93,7 +91,7 @@ type Config struct {
|
|||||||
|
|
||||||
// NewServer initializes and configures a new Server instance
|
// NewServer initializes and configures a new Server instance
|
||||||
func NewServer(cfg *Config) *BaseServer {
|
func NewServer(cfg *Config) *BaseServer {
|
||||||
s := &BaseServer{
|
return &BaseServer{
|
||||||
Config: cfg.NbConfig,
|
Config: cfg.NbConfig,
|
||||||
container: make(map[string]any),
|
container: make(map[string]any),
|
||||||
dnsDomain: cfg.DNSDomain,
|
dnsDomain: cfg.DNSDomain,
|
||||||
@@ -106,9 +104,6 @@ func NewServer(cfg *Config) *BaseServer {
|
|||||||
mgmtMetricsPort: cfg.MgmtMetricsPort,
|
mgmtMetricsPort: cfg.MgmtMetricsPort,
|
||||||
autoResolveDomains: cfg.AutoResolveDomains,
|
autoResolveDomains: cfg.AutoResolveDomains,
|
||||||
}
|
}
|
||||||
s.container[ContainerKeyBaseServer] = s
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BaseServer) AfterInit(fn func(s *BaseServer)) {
|
func (s *BaseServer) AfterInit(fn func(s *BaseServer)) {
|
||||||
@@ -193,7 +188,7 @@ func (s *BaseServer) Start(ctx context.Context) error {
|
|||||||
log.WithContext(srvCtx).Infof("running gRPC backward compatibility server: %s", compatListener.Addr().String())
|
log.WithContext(srvCtx).Infof("running gRPC backward compatibility server: %s", compatListener.Addr().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
rootHandler := s.handlerFunc(srvCtx, s.GRPCServer(), s.APIHandler(), s.IDPHandler(), s.Metrics().GetMeter())
|
rootHandler := s.handlerFunc(srvCtx, s.GRPCServer(), s.APIHandler(), s.Metrics().GetMeter())
|
||||||
switch {
|
switch {
|
||||||
case s.certManager != nil:
|
case s.certManager != nil:
|
||||||
// a call to certManager.Listener() always creates a new listener so we do it once
|
// a call to certManager.Listener() always creates a new listener so we do it once
|
||||||
@@ -304,7 +299,7 @@ func (s *BaseServer) SetHandlerFunc(handler http.Handler) {
|
|||||||
log.Tracef("custom handler set successfully")
|
log.Tracef("custom handler set successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BaseServer) handlerFunc(_ context.Context, gRPCHandler *grpc.Server, httpHandler http.Handler, idpHandler http.Handler, meter metric.Meter) http.Handler {
|
func (s *BaseServer) handlerFunc(_ context.Context, gRPCHandler *grpc.Server, httpHandler http.Handler, meter metric.Meter) http.Handler {
|
||||||
// Check if a custom handler was set (for multiplexing additional services)
|
// Check if a custom handler was set (for multiplexing additional services)
|
||||||
if customHandler, ok := s.GetContainer("customHandler"); ok {
|
if customHandler, ok := s.GetContainer("customHandler"); ok {
|
||||||
if handler, ok := customHandler.(http.Handler); ok {
|
if handler, ok := customHandler.(http.Handler); ok {
|
||||||
@@ -323,8 +318,6 @@ func (s *BaseServer) handlerFunc(_ context.Context, gRPCHandler *grpc.Server, ht
|
|||||||
gRPCHandler.ServeHTTP(writer, request)
|
gRPCHandler.ServeHTTP(writer, request)
|
||||||
case request.URL.Path == wsproxy.ProxyPath+wsproxy.ManagementComponent:
|
case request.URL.Path == wsproxy.ProxyPath+wsproxy.ManagementComponent:
|
||||||
wsProxy.Handler().ServeHTTP(writer, request)
|
wsProxy.Handler().ServeHTTP(writer, request)
|
||||||
case idpHandler != nil && strings.HasPrefix(request.URL.Path, "/oauth2"):
|
|
||||||
idpHandler.ServeHTTP(writer, request)
|
|
||||||
default:
|
default:
|
||||||
httpHandler.ServeHTTP(writer, request)
|
httpHandler.ServeHTTP(writer, request)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
goproto "google.golang.org/protobuf/proto"
|
goproto "google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
|
|
||||||
integrationsConfig "github.com/netbirdio/management-integrations/integrations/config"
|
integrationsConfig "github.com/netbirdio/management-integrations/integrations/config"
|
||||||
|
|
||||||
@@ -187,38 +185,9 @@ func ToSyncResponse(ctx context.Context, config *nbconfig.Config, httpConfig *nb
|
|||||||
response.NetworkMap.SshAuth = &proto.SSHAuth{AuthorizedUsers: hashedUsers, MachineUsers: machineUsers, UserIDClaim: userIDClaim}
|
response.NetworkMap.SshAuth = &proto.SSHAuth{AuthorizedUsers: hashedUsers, MachineUsers: machineUsers, UserIDClaim: userIDClaim}
|
||||||
}
|
}
|
||||||
|
|
||||||
// settings == nil → field stays nil → "no info in this snapshot", client
|
|
||||||
// preserves the deadline it already had. settings non-nil → emit either a
|
|
||||||
// valid deadline or the explicit-zero "disabled" sentinel via
|
|
||||||
// encodeSessionExpiresAt.
|
|
||||||
if settings != nil {
|
|
||||||
response.SessionExpiresAt = encodeSessionExpiresAt(
|
|
||||||
peer.SessionExpiresAt(settings.PeerLoginExpirationEnabled, settings.PeerLoginExpiration),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeSessionExpiresAt encodes a server-side deadline into the 3-state wire
|
|
||||||
// representation used on LoginResponse, SyncResponse and
|
|
||||||
// ExtendAuthSessionResponse. See the proto comments on those messages.
|
|
||||||
//
|
|
||||||
// - deadline.IsZero() → returns &Timestamp{} (seconds=0, nanos=0): the
|
|
||||||
// "expiry disabled or peer is not SSO-tracked" sentinel; the client clears
|
|
||||||
// its anchor.
|
|
||||||
// - deadline non-zero → returns timestamppb.New(deadline): the new absolute
|
|
||||||
// UTC deadline.
|
|
||||||
//
|
|
||||||
// Returning nil ("no info, preserve client's anchor") is the caller's job —
|
|
||||||
// only meaningful on Sync builds where settings were not resolved.
|
|
||||||
func encodeSessionExpiresAt(deadline time.Time) *timestamppb.Timestamp {
|
|
||||||
if deadline.IsZero() {
|
|
||||||
return ×tamppb.Timestamp{}
|
|
||||||
}
|
|
||||||
return timestamppb.New(deadline)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildAuthorizedUsersProto(ctx context.Context, authorizedUsers map[string]map[string]struct{}) ([][]byte, map[string]*proto.MachineUserIndexes) {
|
func buildAuthorizedUsersProto(ctx context.Context, authorizedUsers map[string]map[string]struct{}) ([][]byte, map[string]*proto.MachineUserIndexes) {
|
||||||
userIDToIndex := make(map[string]uint32)
|
userIDToIndex := make(map[string]uint32)
|
||||||
var hashedUsers [][]byte
|
var hashedUsers [][]byte
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
@@ -201,29 +200,3 @@ func TestBuildJWTConfig_Audiences(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestEncodeSessionExpiresAt pins the wire encoding the client's
|
|
||||||
// applySessionDeadline depends on:
|
|
||||||
//
|
|
||||||
// - zero deadline → &Timestamp{} (seconds=0, nanos=0): the explicit
|
|
||||||
// "expiry disabled or peer is not SSO-tracked" sentinel.
|
|
||||||
// - non-zero → timestamppb.New(deadline): the absolute UTC deadline.
|
|
||||||
//
|
|
||||||
// The third state (nil pointer = "no info in this snapshot") is the caller's
|
|
||||||
// responsibility on the Sync path when settings could not be resolved; the
|
|
||||||
// helper itself never returns nil.
|
|
||||||
func TestEncodeSessionExpiresAt(t *testing.T) {
|
|
||||||
t.Run("zero deadline encodes as explicit-zero sentinel", func(t *testing.T) {
|
|
||||||
got := encodeSessionExpiresAt(time.Time{})
|
|
||||||
assert.NotNil(t, got, "must not return nil; nil means 'no info', not 'disabled'")
|
|
||||||
assert.Equal(t, int64(0), got.GetSeconds())
|
|
||||||
assert.Equal(t, int32(0), got.GetNanos())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("non-zero deadline round-trips", func(t *testing.T) {
|
|
||||||
deadline := time.Date(2030, 1, 2, 3, 4, 5, 0, time.UTC)
|
|
||||||
got := encodeSessionExpiresAt(deadline)
|
|
||||||
assert.NotNil(t, got)
|
|
||||||
assert.True(t, got.AsTime().Equal(deadline))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -351,7 +351,6 @@ func (s *ProxyServiceServer) registerProxyConnection(ctx context.Context, params
|
|||||||
SupportsCustomPorts: c.SupportsCustomPorts,
|
SupportsCustomPorts: c.SupportsCustomPorts,
|
||||||
RequireSubdomain: c.RequireSubdomain,
|
RequireSubdomain: c.RequireSubdomain,
|
||||||
SupportsCrowdsec: c.SupportsCrowdsec,
|
SupportsCrowdsec: c.SupportsCrowdsec,
|
||||||
Private: c.Private,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -755,11 +754,6 @@ func (s *ProxyServiceServer) SendServiceUpdate(update *proto.GetMappingUpdateRes
|
|||||||
InitialSyncComplete: update.InitialSyncComplete,
|
InitialSyncComplete: update.InitialSyncComplete,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Drop mappings the proxy lacks capability for (e.g. private without SupportsPrivateService).
|
|
||||||
connUpdate = filterMappingsForProxy(conn, connUpdate)
|
|
||||||
if connUpdate == nil || len(connUpdate.Mapping) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
resp := s.perProxyMessage(connUpdate, conn.proxyID)
|
resp := s.perProxyMessage(connUpdate, conn.proxyID)
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
log.Warnf("Token generation failed for proxy %s, disconnecting to force resync", conn.proxyID)
|
log.Warnf("Token generation failed for proxy %s, disconnecting to force resync", conn.proxyID)
|
||||||
@@ -888,20 +882,16 @@ func (s *ProxyServiceServer) SendServiceUpdateToCluster(ctx context.Context, upd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// proxyAcceptsMapping returns whether the proxy can receive this mapping.
|
// proxyAcceptsMapping returns whether the proxy should receive this mapping.
|
||||||
// Private mappings require SupportsPrivateService; custom-port L4 mappings
|
// Old proxies that never reported capabilities are skipped for non-TLS L4
|
||||||
// require SupportsCustomPorts. Remove operations always pass so proxies can
|
// mappings with a custom listen port, since they don't understand the
|
||||||
// clean up.
|
// protocol. Proxies that report capabilities (even SupportsCustomPorts=false)
|
||||||
|
// are new enough to handle the mapping. TLS uses SNI routing and works on
|
||||||
|
// any proxy. Delete operations are always sent so proxies can clean up.
|
||||||
func proxyAcceptsMapping(conn *proxyConnection, mapping *proto.ProxyMapping) bool {
|
func proxyAcceptsMapping(conn *proxyConnection, mapping *proto.ProxyMapping) bool {
|
||||||
if mapping.Type == proto.ProxyMappingUpdateType_UPDATE_TYPE_REMOVED {
|
if mapping.Type == proto.ProxyMappingUpdateType_UPDATE_TYPE_REMOVED {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if mapping.GetPrivate() {
|
|
||||||
caps := conn.capabilities
|
|
||||||
if caps == nil || caps.SupportsPrivateService == nil || !*caps.SupportsPrivateService {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if mapping.ListenPort == 0 || mapping.Mode == "tls" {
|
if mapping.ListenPort == 0 || mapping.Mode == "tls" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -910,29 +900,6 @@ func proxyAcceptsMapping(conn *proxyConnection, mapping *proto.ProxyMapping) boo
|
|||||||
return conn.capabilities != nil && conn.capabilities.SupportsCustomPorts != nil
|
return conn.capabilities != nil && conn.capabilities.SupportsCustomPorts != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterMappingsForProxy drops mappings the proxy cannot safely receive
|
|
||||||
// (e.g. private mappings to a proxy without SupportsPrivateService).
|
|
||||||
// Returns the input unchanged when no filtering is needed.
|
|
||||||
func filterMappingsForProxy(conn *proxyConnection, update *proto.GetMappingUpdateResponse) *proto.GetMappingUpdateResponse {
|
|
||||||
if update == nil || len(update.Mapping) == 0 {
|
|
||||||
return update
|
|
||||||
}
|
|
||||||
kept := make([]*proto.ProxyMapping, 0, len(update.Mapping))
|
|
||||||
for _, m := range update.Mapping {
|
|
||||||
if !proxyAcceptsMapping(conn, m) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
kept = append(kept, m)
|
|
||||||
}
|
|
||||||
if len(kept) == len(update.Mapping) {
|
|
||||||
return update
|
|
||||||
}
|
|
||||||
return &proto.GetMappingUpdateResponse{
|
|
||||||
Mapping: kept,
|
|
||||||
InitialSyncComplete: update.InitialSyncComplete,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// perProxyMessage returns a copy of update with a fresh one-time token for
|
// perProxyMessage returns a copy of update with a fresh one-time token for
|
||||||
// create/update operations. For delete operations the original mapping is
|
// create/update operations. For delete operations the original mapping is
|
||||||
// used unchanged because proxies do not need to authenticate for removal.
|
// used unchanged because proxies do not need to authenticate for removal.
|
||||||
@@ -994,10 +961,7 @@ func (s *ProxyServiceServer) Authenticate(ctx context.Context, req *proto.Authen
|
|||||||
|
|
||||||
authenticated, userId, method := s.authenticateRequest(ctx, req, service)
|
authenticated, userId, method := s.authenticateRequest(ctx, req, service)
|
||||||
|
|
||||||
// Non-OIDC schemes (PIN/Password/Header) authenticate against per-service
|
token, err := s.generateSessionToken(ctx, authenticated, service, userId, method)
|
||||||
// secrets and have no user-level group context, so groups stay nil. Email
|
|
||||||
// is also empty — these schemes don't resolve a user record at sign time.
|
|
||||||
token, err := s.generateSessionToken(ctx, authenticated, service, userId, "", method, nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1086,7 +1050,7 @@ func (s *ProxyServiceServer) logAuthenticationError(ctx context.Context, err err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProxyServiceServer) generateSessionToken(ctx context.Context, authenticated bool, service *rpservice.Service, userId, userEmail string, method proxyauth.Method, groupIDs, groupNames []string) (string, error) {
|
func (s *ProxyServiceServer) generateSessionToken(ctx context.Context, authenticated bool, service *rpservice.Service, userId string, method proxyauth.Method) (string, error) {
|
||||||
if !authenticated || service.SessionPrivateKey == "" {
|
if !authenticated || service.SessionPrivateKey == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -1094,11 +1058,8 @@ func (s *ProxyServiceServer) generateSessionToken(ctx context.Context, authentic
|
|||||||
token, err := sessionkey.SignToken(
|
token, err := sessionkey.SignToken(
|
||||||
service.SessionPrivateKey,
|
service.SessionPrivateKey,
|
||||||
userId,
|
userId,
|
||||||
userEmail,
|
|
||||||
service.Domain,
|
service.Domain,
|
||||||
method,
|
method,
|
||||||
groupIDs,
|
|
||||||
groupNames,
|
|
||||||
proxyauth.DefaultSessionExpiry,
|
proxyauth.DefaultSessionExpiry,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1109,26 +1070,6 @@ func (s *ProxyServiceServer) generateSessionToken(ctx context.Context, authentic
|
|||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pairGroupIDsAndNames splits a slice of resolved *types.Group records
|
|
||||||
// into parallel id and name slices. ids[i] and names[i] always pair to
|
|
||||||
// the same group. nil entries (orphan ids the manager couldn't resolve)
|
|
||||||
// are skipped so the consumer can rely on positional pairing.
|
|
||||||
func pairGroupIDsAndNames(groups []*types.Group) (ids, names []string) {
|
|
||||||
if len(groups) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
ids = make([]string, 0, len(groups))
|
|
||||||
names = make([]string, 0, len(groups))
|
|
||||||
for _, g := range groups {
|
|
||||||
if g == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ids = append(ids, g.ID)
|
|
||||||
names = append(names, g.Name)
|
|
||||||
}
|
|
||||||
return ids, names
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendStatusUpdate handles status updates from proxy clients.
|
// SendStatusUpdate handles status updates from proxy clients.
|
||||||
func (s *ProxyServiceServer) SendStatusUpdate(ctx context.Context, req *proto.SendStatusUpdateRequest) (*proto.SendStatusUpdateResponse, error) {
|
func (s *ProxyServiceServer) SendStatusUpdate(ctx context.Context, req *proto.SendStatusUpdateRequest) (*proto.SendStatusUpdateResponse, error) {
|
||||||
if err := enforceAccountScope(ctx, req.GetAccountId()); err != nil {
|
if err := enforceAccountScope(ctx, req.GetAccountId()); err != nil {
|
||||||
@@ -1393,9 +1334,7 @@ func (s *ProxyServiceServer) ValidateState(state string) (verifier, redirectURL
|
|||||||
return verifier, redirectURL, nil
|
return verifier, redirectURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateSessionToken creates a signed session JWT for the given domain and
|
// GenerateSessionToken creates a signed session JWT for the given domain and user.
|
||||||
// user. The user's group memberships are embedded in the token so policy-aware
|
|
||||||
// middlewares on the proxy can authorise without an extra management round-trip.
|
|
||||||
func (s *ProxyServiceServer) GenerateSessionToken(ctx context.Context, domain, userID string, method proxyauth.Method) (string, error) {
|
func (s *ProxyServiceServer) GenerateSessionToken(ctx context.Context, domain, userID string, method proxyauth.Method) (string, error) {
|
||||||
service, err := s.getServiceByDomain(ctx, domain)
|
service, err := s.getServiceByDomain(ctx, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1406,29 +1345,11 @@ func (s *ProxyServiceServer) GenerateSessionToken(ctx context.Context, domain, u
|
|||||||
return "", fmt.Errorf("no session key configured for domain: %s", domain)
|
return "", fmt.Errorf("no session key configured for domain: %s", domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
email string
|
|
||||||
groupIDs []string
|
|
||||||
groupNames []string
|
|
||||||
)
|
|
||||||
if s.usersManager != nil {
|
|
||||||
user, userGroups, uerr := s.usersManager.GetUserWithGroups(ctx, userID)
|
|
||||||
if uerr != nil {
|
|
||||||
log.WithContext(ctx).Debugf("session token mint: lookup user %s: %v", userID, uerr)
|
|
||||||
} else if user != nil {
|
|
||||||
email = user.Email
|
|
||||||
groupIDs, groupNames = pairGroupIDsAndNames(userGroups)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessionkey.SignToken(
|
return sessionkey.SignToken(
|
||||||
service.SessionPrivateKey,
|
service.SessionPrivateKey,
|
||||||
userID,
|
userID,
|
||||||
email,
|
|
||||||
domain,
|
domain,
|
||||||
method,
|
method,
|
||||||
groupIDs,
|
|
||||||
groupNames,
|
|
||||||
proxyauth.DefaultSessionExpiry,
|
proxyauth.DefaultSessionExpiry,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1532,7 +1453,7 @@ func (s *ProxyServiceServer) ValidateSession(ctx context.Context, req *proto.Val
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
userID, _, _, _, _, err := proxyauth.ValidateSessionJWT(sessionToken, domain, pubKeyBytes)
|
userID, _, err := proxyauth.ValidateSessionJWT(sessionToken, domain, pubKeyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"domain": domain,
|
"domain": domain,
|
||||||
@@ -1545,7 +1466,7 @@ func (s *ProxyServiceServer) ValidateSession(ctx context.Context, req *proto.Val
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
user, userGroups, err := s.usersManager.GetUserWithGroups(ctx, userID)
|
user, err := s.usersManager.GetUser(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"domain": domain,
|
"domain": domain,
|
||||||
@@ -1579,15 +1500,12 @@ func (s *ProxyServiceServer) ValidateSession(ctx context.Context, req *proto.Val
|
|||||||
"user_id": userID,
|
"user_id": userID,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
}).Debug("ValidateSession: access denied")
|
}).Debug("ValidateSession: access denied")
|
||||||
groupIDs, groupNames := pairGroupIDsAndNames(userGroups)
|
|
||||||
//nolint:nilerr
|
//nolint:nilerr
|
||||||
return &proto.ValidateSessionResponse{
|
return &proto.ValidateSessionResponse{
|
||||||
Valid: false,
|
Valid: false,
|
||||||
UserId: user.Id,
|
UserId: user.Id,
|
||||||
UserEmail: user.Email,
|
UserEmail: user.Email,
|
||||||
DeniedReason: "not_in_group",
|
DeniedReason: "not_in_group",
|
||||||
PeerGroupIds: groupIDs,
|
|
||||||
PeerGroupNames: groupNames,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1597,13 +1515,10 @@ func (s *ProxyServiceServer) ValidateSession(ctx context.Context, req *proto.Val
|
|||||||
"email": user.Email,
|
"email": user.Email,
|
||||||
}).Debug("ValidateSession: access granted")
|
}).Debug("ValidateSession: access granted")
|
||||||
|
|
||||||
groupIDs, groupNames := pairGroupIDsAndNames(userGroups)
|
|
||||||
return &proto.ValidateSessionResponse{
|
return &proto.ValidateSessionResponse{
|
||||||
Valid: true,
|
Valid: true,
|
||||||
UserId: user.Id,
|
UserId: user.Id,
|
||||||
UserEmail: user.Email,
|
UserEmail: user.Email,
|
||||||
PeerGroupIds: groupIDs,
|
|
||||||
PeerGroupNames: groupNames,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1636,154 +1551,3 @@ func (s *ProxyServiceServer) checkGroupAccess(service *rpservice.Service, user *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ptr[T any](v T) *T { return &v }
|
func ptr[T any](v T) *T { return &v }
|
||||||
|
|
||||||
// ValidateTunnelPeer resolves an inbound peer by its WireGuard tunnel IP and
|
|
||||||
// checks the peer's group membership against the service's access groups.
|
|
||||||
// Peers without a user (machine agents, automation workloads) are first-class
|
|
||||||
// callers; authorisation runs off peer-group memberships rather than the
|
|
||||||
// optional owning user's auto-groups. On success a session JWT is minted so
|
|
||||||
// the proxy can install a cookie and skip subsequent management round-trips.
|
|
||||||
func (s *ProxyServiceServer) ValidateTunnelPeer(ctx context.Context, req *proto.ValidateTunnelPeerRequest) (*proto.ValidateTunnelPeerResponse, error) {
|
|
||||||
domain := req.GetDomain()
|
|
||||||
tunnelIPStr := req.GetTunnelIp()
|
|
||||||
|
|
||||||
if domain == "" || tunnelIPStr == "" {
|
|
||||||
return &proto.ValidateTunnelPeerResponse{
|
|
||||||
Valid: false,
|
|
||||||
DeniedReason: "missing domain or tunnel_ip",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tunnelIP := net.ParseIP(tunnelIPStr)
|
|
||||||
if tunnelIP == nil {
|
|
||||||
return &proto.ValidateTunnelPeerResponse{
|
|
||||||
Valid: false,
|
|
||||||
DeniedReason: "invalid_tunnel_ip",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
service, err := s.getServiceByDomain(ctx, domain)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{"domain": domain, "error": err.Error()}).Debug("ValidateTunnelPeer: service not found")
|
|
||||||
//nolint:nilerr
|
|
||||||
return &proto.ValidateTunnelPeerResponse{
|
|
||||||
Valid: false,
|
|
||||||
DeniedReason: "service_not_found",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mirror ValidateSession: account-scoped (BYOP) proxy tokens may only
|
|
||||||
// validate and mint session cookies for their own account's domains.
|
|
||||||
if err := enforceAccountScope(ctx, service.AccountID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
peer, err := s.peersManager.GetPeerByTunnelIP(ctx, service.AccountID, tunnelIP)
|
|
||||||
if err != nil || peer == nil {
|
|
||||||
log.WithFields(log.Fields{"domain": domain, "tunnel_ip": tunnelIPStr}).Debug("ValidateTunnelPeer: peer not found")
|
|
||||||
//nolint:nilerr
|
|
||||||
return &proto.ValidateTunnelPeerResponse{
|
|
||||||
Valid: false,
|
|
||||||
DeniedReason: "peer_not_found",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, peerGroups, err := s.peersManager.GetPeerWithGroups(ctx, service.AccountID, peer.ID)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{"domain": domain, "peer_id": peer.ID, "error": err.Error()}).Debug("ValidateTunnelPeer: peer groups lookup failed")
|
|
||||||
//nolint:nilerr
|
|
||||||
return &proto.ValidateTunnelPeerResponse{
|
|
||||||
Valid: false,
|
|
||||||
DeniedReason: "peer_not_found",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
groupIDs, groupNames := pairGroupIDsAndNames(peerGroups)
|
|
||||||
|
|
||||||
// Resolve the principal: when the peer is linked to a user, the human
|
|
||||||
// is the principal so multiple peers owned by the same user share a
|
|
||||||
// single identity. Unlinked peers (machine agents) are their own
|
|
||||||
// principal keyed on peer.ID. displayIdentity is what upstream gateways
|
|
||||||
// tag spend with — user.Email when linked, peer.Name when not.
|
|
||||||
principalID := peer.ID
|
|
||||||
displayIdentity := peer.Name
|
|
||||||
if peer.UserID != "" {
|
|
||||||
if user, uerr := s.usersManager.GetUser(ctx, peer.UserID); uerr == nil && user != nil {
|
|
||||||
principalID = user.Id
|
|
||||||
if user.Email != "" {
|
|
||||||
displayIdentity = user.Email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkPeerGroupAccess(service, groupIDs); err != nil {
|
|
||||||
log.WithFields(log.Fields{"domain": domain, "peer_id": peer.ID, "error": err.Error()}).Debug("ValidateTunnelPeer: access denied")
|
|
||||||
//nolint:nilerr
|
|
||||||
return &proto.ValidateTunnelPeerResponse{
|
|
||||||
Valid: false,
|
|
||||||
UserId: principalID,
|
|
||||||
UserEmail: displayIdentity,
|
|
||||||
DeniedReason: "not_in_group",
|
|
||||||
PeerGroupIds: groupIDs,
|
|
||||||
PeerGroupNames: groupNames,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := s.generateSessionToken(ctx, true, service, principalID, displayIdentity, proxyauth.MethodOIDC, groupIDs, groupNames)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"domain": domain,
|
|
||||||
"tunnel_ip": tunnelIPStr,
|
|
||||||
"peer_id": peer.ID,
|
|
||||||
"principal_id": principalID,
|
|
||||||
}).Debug("ValidateTunnelPeer: access granted")
|
|
||||||
|
|
||||||
return &proto.ValidateTunnelPeerResponse{
|
|
||||||
Valid: true,
|
|
||||||
UserId: principalID,
|
|
||||||
UserEmail: displayIdentity,
|
|
||||||
SessionToken: token,
|
|
||||||
PeerGroupIds: groupIDs,
|
|
||||||
PeerGroupNames: groupNames,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkPeerGroupAccess gates ValidateTunnelPeer by the service's required
|
|
||||||
// groups. Private services authorise against AccessGroups (empty list fails
|
|
||||||
// closed — Validate() rejects that at save time but the RPC is the security
|
|
||||||
// boundary and must not trust upstream state). Bearer-auth services authorise
|
|
||||||
// against DistributionGroups when populated. Non-private non-bearer services
|
|
||||||
// are open.
|
|
||||||
func checkPeerGroupAccess(service *rpservice.Service, peerGroupIDs []string) error {
|
|
||||||
if service.Private {
|
|
||||||
if len(service.AccessGroups) == 0 {
|
|
||||||
return fmt.Errorf("private service has no access groups")
|
|
||||||
}
|
|
||||||
return matchAnyGroup(service.AccessGroups, peerGroupIDs)
|
|
||||||
}
|
|
||||||
if service.Auth.BearerAuth != nil && service.Auth.BearerAuth.Enabled && len(service.Auth.BearerAuth.DistributionGroups) > 0 {
|
|
||||||
return matchAnyGroup(service.Auth.BearerAuth.DistributionGroups, peerGroupIDs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchAnyGroup returns nil when peerGroupIDs intersects allowedGroups,
|
|
||||||
// else a non-nil error.
|
|
||||||
func matchAnyGroup(allowedGroups, peerGroupIDs []string) error {
|
|
||||||
if len(allowedGroups) == 0 {
|
|
||||||
return fmt.Errorf("no allowed groups configured")
|
|
||||||
}
|
|
||||||
allowed := make(map[string]struct{}, len(allowedGroups))
|
|
||||||
for _, g := range allowedGroups {
|
|
||||||
allowed[g] = struct{}{}
|
|
||||||
}
|
|
||||||
for _, g := range peerGroupIDs {
|
|
||||||
if _, ok := allowed[g]; ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("peer not in allowed groups")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -129,14 +129,6 @@ func (m *mockUsersManager) GetUser(ctx context.Context, userID string) (*types.U
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockUsersManager) GetUserWithGroups(ctx context.Context, userID string) (*types.User, []*types.Group, error) {
|
|
||||||
user, err := m.GetUser(ctx, userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return user, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateUserGroupAccess(t *testing.T) {
|
func TestValidateUserGroupAccess(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -428,46 +420,3 @@ func TestGetAccountProxyByDomain(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckPeerGroupAccess(t *testing.T) {
|
|
||||||
t.Run("private with empty AccessGroups denies", func(t *testing.T) {
|
|
||||||
svc := &service.Service{Private: true, AccessGroups: nil}
|
|
||||||
err := checkPeerGroupAccess(svc, []string{"grp-admins"})
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Contains(t, err.Error(), "no access groups")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("private with peer in AccessGroups allows", func(t *testing.T) {
|
|
||||||
svc := &service.Service{Private: true, AccessGroups: []string{"grp-admins", "grp-ops"}}
|
|
||||||
assert.NoError(t, checkPeerGroupAccess(svc, []string{"grp-other", "grp-ops"}))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("private with peer outside AccessGroups denies", func(t *testing.T) {
|
|
||||||
svc := &service.Service{Private: true, AccessGroups: []string{"grp-admins"}}
|
|
||||||
assert.Error(t, checkPeerGroupAccess(svc, []string{"grp-other"}))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("bearer enabled with empty DistributionGroups allows", func(t *testing.T) {
|
|
||||||
svc := &service.Service{
|
|
||||||
Auth: service.AuthConfig{BearerAuth: &service.BearerAuthConfig{Enabled: true}},
|
|
||||||
}
|
|
||||||
assert.NoError(t, checkPeerGroupAccess(svc, []string{"grp-anyone"}))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("bearer enabled gates on DistributionGroups", func(t *testing.T) {
|
|
||||||
svc := &service.Service{
|
|
||||||
Auth: service.AuthConfig{
|
|
||||||
BearerAuth: &service.BearerAuthConfig{
|
|
||||||
Enabled: true,
|
|
||||||
DistributionGroups: []string{"grp-allowed"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.NoError(t, checkPeerGroupAccess(svc, []string{"grp-allowed"}))
|
|
||||||
assert.Error(t, checkPeerGroupAccess(svc, []string{"grp-other"}))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("non-private non-bearer is open", func(t *testing.T) {
|
|
||||||
assert.NoError(t, checkPeerGroupAccess(&service.Service{}, nil))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -437,7 +437,7 @@ func (s *Server) handleUpdates(ctx context.Context, accountID string, peerKey wg
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithContext(ctx).Tracef("received an update for peer %s", peerKey.String())
|
log.WithContext(ctx).Debugf("received an update for peer %s", peerKey.String())
|
||||||
if debouncer.ProcessUpdate(update) {
|
if debouncer.ProcessUpdate(update) {
|
||||||
// Send immediately (first update or after quiet period)
|
// Send immediately (first update or after quiet period)
|
||||||
if err := s.sendUpdate(ctx, accountID, peerKey, peer, update, srv, streamStartTime); err != nil {
|
if err := s.sendUpdate(ctx, accountID, peerKey, peer, update, srv, streamStartTime); err != nil {
|
||||||
@@ -492,7 +492,7 @@ func (s *Server) sendUpdate(ctx context.Context, accountID string, peerKey wgtyp
|
|||||||
s.cancelPeerRoutines(ctx, accountID, peer, streamStartTime)
|
s.cancelPeerRoutines(ctx, accountID, peer, streamStartTime)
|
||||||
return status.Errorf(codes.Internal, "failed sending update message")
|
return status.Errorf(codes.Internal, "failed sending update message")
|
||||||
}
|
}
|
||||||
log.WithContext(ctx).Tracef("sent an update to peer %s", peerKey.String())
|
log.WithContext(ctx).Debugf("sent an update to peer %s", peerKey.String())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,80 +821,6 @@ func (s *Server) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtendAuthSession refreshes the peer's SSO session expiry deadline using a
|
|
||||||
// fresh JWT. The same JWT validation pipeline as Login is used. The tunnel
|
|
||||||
// stays up; no network map sync is performed. The new deadline is returned
|
|
||||||
// in ExtendAuthSessionResponse.SessionExpiresAt.
|
|
||||||
func (s *Server) ExtendAuthSession(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
|
||||||
extendReq := &proto.ExtendAuthSessionRequest{}
|
|
||||||
peerKey, err := s.parseRequest(ctx, req, extendReq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint
|
|
||||||
ctx = context.WithValue(ctx, nbContext.PeerIDKey, peerKey.String())
|
|
||||||
if accountID, accErr := s.accountManager.GetAccountIDForPeerKey(ctx, peerKey.String()); accErr == nil {
|
|
||||||
//nolint
|
|
||||||
ctx = context.WithValue(ctx, nbContext.AccountIDKey, accountID)
|
|
||||||
}
|
|
||||||
|
|
||||||
jwt := extendReq.GetJwtToken()
|
|
||||||
if jwt == "" {
|
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "jwt token is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
var userID string
|
|
||||||
const attempts = 3
|
|
||||||
for i := 0; i < attempts; i++ {
|
|
||||||
userID, err = s.validateToken(ctx, peerKey.String(), jwt)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if i == attempts-1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
log.WithContext(ctx).Warnf("failed validating JWT token while extending session for peer %s: %v. Retrying (idP cache).", peerKey.String(), err)
|
|
||||||
select {
|
|
||||||
case <-time.After(200 * time.Millisecond):
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if userID == "" {
|
|
||||||
return nil, status.Errorf(codes.Unauthenticated, "jwt token did not yield a user id")
|
|
||||||
}
|
|
||||||
|
|
||||||
deadline, err := s.accountManager.ExtendPeerSession(ctx, peerKey.String(), userID)
|
|
||||||
if err != nil {
|
|
||||||
log.WithContext(ctx).Warnf("failed extending session for peer %s: %v", peerKey.String(), err)
|
|
||||||
return nil, mapError(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success path normally returns a non-zero deadline. A defensive zero
|
|
||||||
// would still encode as the explicit "disabled" sentinel rather than nil,
|
|
||||||
// so the client clears any stale anchor instead of preserving it.
|
|
||||||
resp := &proto.ExtendAuthSessionResponse{
|
|
||||||
SessionExpiresAt: encodeSessionExpiresAt(deadline),
|
|
||||||
}
|
|
||||||
|
|
||||||
wgKey, err := s.secretsManager.GetWGKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed processing request")
|
|
||||||
}
|
|
||||||
encrypted, err := encryption.EncryptMessage(peerKey, wgKey, resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed encrypting response")
|
|
||||||
}
|
|
||||||
return &proto.EncryptedMessage{
|
|
||||||
WgPubKey: wgKey.PublicKey().String(),
|
|
||||||
Body: encrypted,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) prepareLoginResponse(ctx context.Context, peer *nbpeer.Peer, netMap *types.NetworkMap, postureChecks []*posture.Checks) (*proto.LoginResponse, error) {
|
func (s *Server) prepareLoginResponse(ctx context.Context, peer *nbpeer.Peer, netMap *types.NetworkMap, postureChecks []*posture.Checks) (*proto.LoginResponse, error) {
|
||||||
var relayToken *Token
|
var relayToken *Token
|
||||||
var err error
|
var err error
|
||||||
@@ -918,12 +844,6 @@ func (s *Server) prepareLoginResponse(ctx context.Context, peer *nbpeer.Peer, ne
|
|||||||
Checks: toProtocolChecks(ctx, postureChecks),
|
Checks: toProtocolChecks(ctx, postureChecks),
|
||||||
}
|
}
|
||||||
|
|
||||||
// settings is always non-nil here, so we never emit nil — encoder returns
|
|
||||||
// either a valid deadline or the explicit-zero "disabled" sentinel.
|
|
||||||
loginResp.SessionExpiresAt = encodeSessionExpiresAt(
|
|
||||||
peer.SessionExpiresAt(settings.PeerLoginExpirationEnabled, settings.PeerLoginExpiration),
|
|
||||||
)
|
|
||||||
|
|
||||||
return loginResp, nil
|
return loginResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ func generateSessionKeyPair(t *testing.T) (string, string) {
|
|||||||
|
|
||||||
func createSessionToken(t *testing.T, privKeyB64, userID, domain string) string {
|
func createSessionToken(t *testing.T, privKeyB64, userID, domain string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
token, err := sessionkey.SignToken(privKeyB64, userID, domain, auth.MethodOIDC, nil, time.Hour)
|
token, err := sessionkey.SignToken(privKeyB64, userID, domain, auth.MethodOIDC, time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,6 @@ func TestValidateSession_UserAllowed(t *testing.T) {
|
|||||||
assert.True(t, resp.Valid, "User should be allowed access")
|
assert.True(t, resp.Valid, "User should be allowed access")
|
||||||
assert.Equal(t, "allowedUserId", resp.UserId)
|
assert.Equal(t, "allowedUserId", resp.UserId)
|
||||||
assert.Empty(t, resp.DeniedReason)
|
assert.Empty(t, resp.DeniedReason)
|
||||||
assert.Equal(t, []string{"allowedGroupId"}, resp.GetPeerGroupIds(), "PeerGroupIds must mirror the resolved user's group memberships")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateSession_UserNotInAllowedGroup(t *testing.T) {
|
func TestValidateSession_UserNotInAllowedGroup(t *testing.T) {
|
||||||
@@ -146,7 +145,6 @@ func TestValidateSession_UserNotInAllowedGroup(t *testing.T) {
|
|||||||
assert.False(t, resp.Valid, "User not in group should be denied")
|
assert.False(t, resp.Valid, "User not in group should be denied")
|
||||||
assert.Equal(t, "not_in_group", resp.DeniedReason)
|
assert.Equal(t, "not_in_group", resp.DeniedReason)
|
||||||
assert.Equal(t, "nonGroupUserId", resp.UserId)
|
assert.Equal(t, "nonGroupUserId", resp.UserId)
|
||||||
assert.Empty(t, resp.GetPeerGroupIds(), "PeerGroupIds must mirror the resolved user's actual (empty) memberships on denial")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateSession_UserInDifferentAccount(t *testing.T) {
|
func TestValidateSession_UserInDifferentAccount(t *testing.T) {
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ func (am *DefaultAccountManager) GetIdpManager() idp.Manager {
|
|||||||
// User that performs the update has to belong to the account.
|
// User that performs the update has to belong to the account.
|
||||||
// Returns an updated Settings
|
// Returns an updated Settings
|
||||||
func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, accountID, userID string, newSettings *types.Settings) (*types.Settings, error) {
|
func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, accountID, userID string, newSettings *types.Settings) (*types.Settings, error) {
|
||||||
allowed, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Settings, operations.Update)
|
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Settings, operations.Update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to validate user permissions: %w", err)
|
return nil, fmt.Errorf("failed to validate user permissions: %w", err)
|
||||||
}
|
}
|
||||||
@@ -355,17 +355,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
|||||||
oldSettings.LazyConnectionEnabled != newSettings.LazyConnectionEnabled ||
|
oldSettings.LazyConnectionEnabled != newSettings.LazyConnectionEnabled ||
|
||||||
oldSettings.DNSDomain != newSettings.DNSDomain ||
|
oldSettings.DNSDomain != newSettings.DNSDomain ||
|
||||||
oldSettings.AutoUpdateVersion != newSettings.AutoUpdateVersion ||
|
oldSettings.AutoUpdateVersion != newSettings.AutoUpdateVersion ||
|
||||||
oldSettings.AutoUpdateAlways != newSettings.AutoUpdateAlways ||
|
oldSettings.AutoUpdateAlways != newSettings.AutoUpdateAlways {
|
||||||
oldSettings.PeerLoginExpirationEnabled != newSettings.PeerLoginExpirationEnabled ||
|
|
||||||
oldSettings.PeerLoginExpiration != newSettings.PeerLoginExpiration {
|
|
||||||
// Session deadline is derived from LastLogin + PeerLoginExpiration
|
|
||||||
// on every Login/Sync response. Without a fan-out push, connected
|
|
||||||
// peers keep the deadline they received at login time and only see
|
|
||||||
// the new value after the next unrelated NetworkMap change. Add
|
|
||||||
// these two fields to the trigger list so admin-side expiry tweaks
|
|
||||||
// (e.g. shortening from 24h to 1h) reach every connected peer
|
|
||||||
// within seconds, which is what the proactive-warning feature
|
|
||||||
// relies on (see client/internal/auth/sessionwatch).
|
|
||||||
updateAccountPeers = true
|
updateAccountPeers = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -855,7 +845,7 @@ func (am *DefaultAccountManager) DeleteAccount(ctx context.Context, accountID, u
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
allowed, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Accounts, operations.Delete)
|
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Accounts, operations.Delete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to validate user permissions: %w", err)
|
return fmt.Errorf("failed to validate user permissions: %w", err)
|
||||||
}
|
}
|
||||||
@@ -1422,7 +1412,7 @@ func (am *DefaultAccountManager) GetAccount(ctx context.Context, accountID strin
|
|||||||
|
|
||||||
// GetAccountByID returns an account associated with this account ID.
|
// GetAccountByID returns an account associated with this account ID.
|
||||||
func (am *DefaultAccountManager) GetAccountByID(ctx context.Context, accountID string, userID string) (*types.Account, error) {
|
func (am *DefaultAccountManager) GetAccountByID(ctx context.Context, accountID string, userID string) (*types.Account, error) {
|
||||||
allowed, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Accounts, operations.Read)
|
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Accounts, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -1435,7 +1425,7 @@ func (am *DefaultAccountManager) GetAccountByID(ctx context.Context, accountID s
|
|||||||
|
|
||||||
// GetAccountMeta returns the account metadata associated with this account ID.
|
// GetAccountMeta returns the account metadata associated with this account ID.
|
||||||
func (am *DefaultAccountManager) GetAccountMeta(ctx context.Context, accountID string, userID string) (*types.AccountMeta, error) {
|
func (am *DefaultAccountManager) GetAccountMeta(ctx context.Context, accountID string, userID string) (*types.AccountMeta, error) {
|
||||||
allowed, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Accounts, operations.Read)
|
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Accounts, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -1448,7 +1438,7 @@ func (am *DefaultAccountManager) GetAccountMeta(ctx context.Context, accountID s
|
|||||||
|
|
||||||
// GetAccountOnboarding retrieves the onboarding information for a specific account.
|
// GetAccountOnboarding retrieves the onboarding information for a specific account.
|
||||||
func (am *DefaultAccountManager) GetAccountOnboarding(ctx context.Context, accountID string, userID string) (*types.AccountOnboarding, error) {
|
func (am *DefaultAccountManager) GetAccountOnboarding(ctx context.Context, accountID string, userID string) (*types.AccountOnboarding, error) {
|
||||||
allowed, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Accounts, operations.Read)
|
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Accounts, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -1473,7 +1463,7 @@ func (am *DefaultAccountManager) GetAccountOnboarding(ctx context.Context, accou
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) UpdateAccountOnboarding(ctx context.Context, accountID, userID string, newOnboarding *types.AccountOnboarding) (*types.AccountOnboarding, error) {
|
func (am *DefaultAccountManager) UpdateAccountOnboarding(ctx context.Context, accountID, userID string, newOnboarding *types.AccountOnboarding) (*types.AccountOnboarding, error) {
|
||||||
allowed, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Settings, operations.Update)
|
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Settings, operations.Update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to validate user permissions: %w", err)
|
return nil, fmt.Errorf("failed to validate user permissions: %w", err)
|
||||||
}
|
}
|
||||||
@@ -1540,8 +1530,7 @@ func (am *DefaultAccountManager) GetAccountIDFromUserAuth(ctx context.Context, u
|
|||||||
return accountID, user.Id, nil
|
return accountID, user.Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, err = am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false)
|
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil {
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1987,7 +1976,7 @@ func (am *DefaultAccountManager) handleUserPeer(ctx context.Context, transaction
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) GetAccountSettings(ctx context.Context, accountID string, userID string) (*types.Settings, error) {
|
func (am *DefaultAccountManager) GetAccountSettings(ctx context.Context, accountID string, userID string) (*types.Settings, error) {
|
||||||
allowed, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Settings, operations.Read)
|
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Settings, operations.Read)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.NewPermissionValidationError(err)
|
return nil, status.NewPermissionValidationError(err)
|
||||||
}
|
}
|
||||||
@@ -2555,7 +2544,7 @@ func (am *DefaultAccountManager) validateIPForUpdate(account *types.Account, pee
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) UpdatePeerIP(ctx context.Context, accountID, userID, peerID string, newIP netip.Addr) error {
|
func (am *DefaultAccountManager) UpdatePeerIP(ctx context.Context, accountID, userID, peerID string, newIP netip.Addr) error {
|
||||||
allowed, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Update)
|
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate user permissions: %w", err)
|
return fmt.Errorf("validate user permissions: %w", err)
|
||||||
}
|
}
|
||||||
@@ -2645,7 +2634,7 @@ func (am *DefaultAccountManager) savePeerIPUpdate(ctx context.Context, transacti
|
|||||||
// UpdatePeerIPv6 updates the IPv6 overlay address of a peer, validating it's
|
// UpdatePeerIPv6 updates the IPv6 overlay address of a peer, validating it's
|
||||||
// within the account's v6 network range and not already taken.
|
// within the account's v6 network range and not already taken.
|
||||||
func (am *DefaultAccountManager) UpdatePeerIPv6(ctx context.Context, accountID, userID, peerID string, newIPv6 netip.Addr) error {
|
func (am *DefaultAccountManager) UpdatePeerIPv6(ctx context.Context, accountID, userID, peerID string, newIPv6 netip.Addr) error {
|
||||||
allowed, ctx, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Update)
|
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate user permissions: %w", err)
|
return fmt.Errorf("validate user permissions: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ type Manager interface {
|
|||||||
UpdateAccountSettings(ctx context.Context, accountID, userID string, newSettings *types.Settings) (*types.Settings, error)
|
UpdateAccountSettings(ctx context.Context, accountID, userID string, newSettings *types.Settings) (*types.Settings, error)
|
||||||
UpdateAccountOnboarding(ctx context.Context, accountID, userID string, newOnboarding *types.AccountOnboarding) (*types.AccountOnboarding, error)
|
UpdateAccountOnboarding(ctx context.Context, accountID, userID string, newOnboarding *types.AccountOnboarding) (*types.AccountOnboarding, error)
|
||||||
LoginPeer(ctx context.Context, login types.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) // used by peer gRPC API
|
LoginPeer(ctx context.Context, login types.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) // used by peer gRPC API
|
||||||
ExtendPeerSession(ctx context.Context, peerPubKey, userID string) (time.Time, error) // used by peer gRPC API for ExtendAuthSession
|
|
||||||
SyncPeer(ctx context.Context, sync types.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, int64, error) // used by peer gRPC API
|
SyncPeer(ctx context.Context, sync types.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, int64, error) // used by peer gRPC API
|
||||||
GetExternalCacheManager() ExternalCacheManager
|
GetExternalCacheManager() ExternalCacheManager
|
||||||
GetPostureChecks(ctx context.Context, accountID, postureChecksID, userID string) (*posture.Checks, error)
|
GetPostureChecks(ctx context.Context, accountID, postureChecksID, userID string) (*posture.Checks, error)
|
||||||
|
|||||||
@@ -1304,21 +1304,6 @@ func (mr *MockManagerMockRecorder) LoginPeer(ctx, login interface{}) *gomock.Cal
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoginPeer", reflect.TypeOf((*MockManager)(nil).LoginPeer), ctx, login)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoginPeer", reflect.TypeOf((*MockManager)(nil).LoginPeer), ctx, login)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtendPeerSession mocks base method.
|
|
||||||
func (m *MockManager) ExtendPeerSession(ctx context.Context, peerPubKey, userID string) (time.Time, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ExtendPeerSession", ctx, peerPubKey, userID)
|
|
||||||
ret0, _ := ret[0].(time.Time)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtendPeerSession indicates an expected call of ExtendPeerSession.
|
|
||||||
func (mr *MockManagerMockRecorder) ExtendPeerSession(ctx, peerPubKey, userID interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtendPeerSession", reflect.TypeOf((*MockManager)(nil).ExtendPeerSession), ctx, peerPubKey, userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkPeerConnected mocks base method.
|
// MarkPeerConnected mocks base method.
|
||||||
func (m *MockManager) MarkPeerConnected(ctx context.Context, peerKey string, realIP net.IP, accountID string, sessionStartedAt int64) error {
|
func (m *MockManager) MarkPeerConnected(ctx context.Context, peerKey string, realIP net.IP, accountID string, sessionStartedAt int64) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
@@ -240,10 +240,6 @@ const (
|
|||||||
AccountLocalMfaEnabled Activity = 123
|
AccountLocalMfaEnabled Activity = 123
|
||||||
// AccountLocalMfaDisabled indicates that a user disabled TOTP MFA for local users
|
// AccountLocalMfaDisabled indicates that a user disabled TOTP MFA for local users
|
||||||
AccountLocalMfaDisabled Activity = 124
|
AccountLocalMfaDisabled Activity = 124
|
||||||
// UserExtendedPeerSession indicates that a user refreshed their peer's
|
|
||||||
// SSO session deadline via ExtendAuthSession without re-establishing the
|
|
||||||
// tunnel. Distinct from UserLoggedInPeer (full interactive login).
|
|
||||||
UserExtendedPeerSession Activity = 125
|
|
||||||
|
|
||||||
AccountDeleted Activity = 99999
|
AccountDeleted Activity = 99999
|
||||||
)
|
)
|
||||||
@@ -398,8 +394,6 @@ var activityMap = map[Activity]Code{
|
|||||||
AccountLocalMfaEnabled: {"Account local MFA enabled", "account.setting.local.mfa.enable"},
|
AccountLocalMfaEnabled: {"Account local MFA enabled", "account.setting.local.mfa.enable"},
|
||||||
AccountLocalMfaDisabled: {"Account local MFA disabled", "account.setting.local.mfa.disable"},
|
AccountLocalMfaDisabled: {"Account local MFA disabled", "account.setting.local.mfa.disable"},
|
||||||
|
|
||||||
UserExtendedPeerSession: {"User extended peer session", "user.peer.session.extend"},
|
|
||||||
|
|
||||||
DomainAdded: {"Domain added", "domain.add"},
|
DomainAdded: {"Domain added", "domain.add"},
|
||||||
DomainDeleted: {"Domain deleted", "domain.delete"},
|
DomainDeleted: {"Domain deleted", "domain.delete"},
|
||||||
DomainValidated: {"Domain validated", "domain.validate"},
|
DomainValidated: {"Domain validated", "domain.validate"},
|
||||||
|
|||||||
@@ -1,27 +1,10 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import "github.com/netbirdio/netbird/shared/context"
|
||||||
"context"
|
|
||||||
|
|
||||||
nbcontext "github.com/netbirdio/netbird/shared/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RequestIDKey = nbcontext.RequestIDKey
|
RequestIDKey = context.RequestIDKey
|
||||||
AccountIDKey = nbcontext.AccountIDKey
|
AccountIDKey = context.AccountIDKey
|
||||||
RoleKey = nbcontext.RoleKey
|
UserIDKey = context.UserIDKey
|
||||||
UserIDKey = nbcontext.UserIDKey
|
PeerIDKey = context.PeerIDKey
|
||||||
PeerIDKey = nbcontext.PeerIDKey
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RoleFromContext returns the role stored in ctx, or empty string and false if absent.
|
|
||||||
func RoleFromContext(ctx context.Context) (string, bool) {
|
|
||||||
role, ok := ctx.Value(RoleKey).(string)
|
|
||||||
return role, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithRole returns a new context carrying the given role.
|
|
||||||
func WithRole(ctx context.Context, role string) context.Context {
|
|
||||||
//nolint
|
|
||||||
return context.WithValue(ctx, RoleKey, role)
|
|
||||||
}
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user