mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-26 12:16:39 +00:00
Compare commits
18 Commits
v0.47.2
...
v0.48.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1010629ea2 | ||
|
|
955d48abb9 | ||
|
|
d6dad20c83 | ||
|
|
04676c5368 | ||
|
|
b53a517bf8 | ||
|
|
f37aa2cc9d | ||
|
|
5343bee7b2 | ||
|
|
870e29db63 | ||
|
|
08e9b05d51 | ||
|
|
3581648071 | ||
|
|
2a51609436 | ||
|
|
83457f8b99 | ||
|
|
b45284f086 | ||
|
|
e9016aecea | ||
|
|
23b5d45b68 | ||
|
|
0e5dc9d412 | ||
|
|
91f7ee6a3c | ||
|
|
7c6b85b4cb |
21
.github/workflows/git-town.yml
vendored
21
.github/workflows/git-town.yml
vendored
@@ -1,21 +0,0 @@
|
|||||||
name: Git Town
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- '**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
git-town:
|
|
||||||
name: Display the branch stack
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: git-town/action@v1
|
|
||||||
with:
|
|
||||||
skip-single-stacks: true
|
|
||||||
46
.github/workflows/golang-test-darwin.yml
vendored
46
.github/workflows/golang-test-darwin.yml
vendored
@@ -1,46 +0,0 @@
|
|||||||
name: "Darwin"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
name: "Client / Unit"
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
cache: false
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ~/go/pkg/mod
|
|
||||||
key: macos-gotest-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
macos-gotest-
|
|
||||||
macos-go-
|
|
||||||
|
|
||||||
- name: Install libpcap
|
|
||||||
run: brew install libpcap
|
|
||||||
|
|
||||||
- name: Install modules
|
|
||||||
run: go mod tidy
|
|
||||||
|
|
||||||
- name: check git status
|
|
||||||
run: git --no-pager diff --exit-code
|
|
||||||
|
|
||||||
- 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 /management)
|
|
||||||
|
|
||||||
52
.github/workflows/golang-test-freebsd.yml
vendored
52
.github/workflows/golang-test-freebsd.yml
vendored
@@ -1,52 +0,0 @@
|
|||||||
name: "FreeBSD"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
name: "Client / Unit"
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Test in FreeBSD
|
|
||||||
id: test
|
|
||||||
uses: vmactions/freebsd-vm@v1
|
|
||||||
with:
|
|
||||||
usesh: true
|
|
||||||
copyback: false
|
|
||||||
release: "14.2"
|
|
||||||
prepare: |
|
|
||||||
pkg install -y curl pkgconf xorg
|
|
||||||
LATEST_VERSION=$(curl -s https://go.dev/VERSION?m=text|head -n 1)
|
|
||||||
GO_TARBALL="$LATEST_VERSION.freebsd-amd64.tar.gz"
|
|
||||||
GO_URL="https://go.dev/dl/$GO_TARBALL"
|
|
||||||
curl -vLO "$GO_URL"
|
|
||||||
tar -C /usr/local -vxzf "$GO_TARBALL"
|
|
||||||
|
|
||||||
# -x - to print all executed commands
|
|
||||||
# -e - to faile on first error
|
|
||||||
run: |
|
|
||||||
set -e -x
|
|
||||||
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
|
|
||||||
time go build -o netbird client/main.go
|
|
||||||
# check all component except management, since we do not support management server on freebsd
|
|
||||||
time go test -timeout 1m -failfast ./base62/...
|
|
||||||
# NOTE: without -p1 `client/internal/dns` will fail because of `listen udp4 :33100: bind: address already in use`
|
|
||||||
time go test -timeout 8m -failfast -p 1 ./client/...
|
|
||||||
time go test -timeout 1m -failfast ./dns/...
|
|
||||||
time go test -timeout 1m -failfast ./encryption/...
|
|
||||||
time go test -timeout 1m -failfast ./formatter/...
|
|
||||||
time go test -timeout 1m -failfast ./client/iface/...
|
|
||||||
time go test -timeout 1m -failfast ./route/...
|
|
||||||
time go test -timeout 1m -failfast ./sharedsock/...
|
|
||||||
time go test -timeout 1m -failfast ./signal/...
|
|
||||||
time go test -timeout 1m -failfast ./util/...
|
|
||||||
time go test -timeout 1m -failfast ./version/...
|
|
||||||
568
.github/workflows/golang-test-linux.yml
vendored
568
.github/workflows/golang-test-linux.yml
vendored
@@ -1,568 +0,0 @@
|
|||||||
name: Linux
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-cache:
|
|
||||||
name: "Build Cache"
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
outputs:
|
|
||||||
management: ${{ steps.filter.outputs.management }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: dorny/paths-filter@v3
|
|
||||||
id: filter
|
|
||||||
with:
|
|
||||||
filters: |
|
|
||||||
management:
|
|
||||||
- 'management/**'
|
|
||||||
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: Get Go environment
|
|
||||||
run: |
|
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
|
||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
|
||||||
uses: actions/cache@v4
|
|
||||||
id: cache
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{ env.cache }}
|
|
||||||
${{ env.modcache }}
|
|
||||||
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev
|
|
||||||
|
|
||||||
- name: Install 32-bit libpcap
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386
|
|
||||||
|
|
||||||
- name: Build client
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
working-directory: client
|
|
||||||
run: CGO_ENABLED=1 go build .
|
|
||||||
|
|
||||||
- name: Build client 386
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
working-directory: client
|
|
||||||
run: CGO_ENABLED=1 GOARCH=386 go build -o client-386 .
|
|
||||||
|
|
||||||
- name: Build management
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
working-directory: management
|
|
||||||
run: CGO_ENABLED=1 go build .
|
|
||||||
|
|
||||||
- name: Build management 386
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
working-directory: management
|
|
||||||
run: CGO_ENABLED=1 GOARCH=386 go build -o management-386 .
|
|
||||||
|
|
||||||
- name: Build signal
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
working-directory: signal
|
|
||||||
run: CGO_ENABLED=1 go build .
|
|
||||||
|
|
||||||
- name: Build signal 386
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
working-directory: signal
|
|
||||||
run: CGO_ENABLED=1 GOARCH=386 go build -o signal-386 .
|
|
||||||
|
|
||||||
- name: Build relay
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
working-directory: relay
|
|
||||||
run: CGO_ENABLED=1 go build .
|
|
||||||
|
|
||||||
- name: Build relay 386
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
working-directory: relay
|
|
||||||
run: CGO_ENABLED=1 GOARCH=386 go build -o relay-386 .
|
|
||||||
|
|
||||||
test:
|
|
||||||
name: "Client / Unit"
|
|
||||||
needs: [build-cache]
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
arch: [ '386','amd64' ]
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get Go environment
|
|
||||||
run: |
|
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
|
||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{ env.cache }}
|
|
||||||
${{ env.modcache }}
|
|
||||||
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gotest-cache-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev
|
|
||||||
|
|
||||||
- name: Install 32-bit libpcap
|
|
||||||
if: matrix.arch == '386'
|
|
||||||
run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386
|
|
||||||
|
|
||||||
- name: Install modules
|
|
||||||
run: go mod tidy
|
|
||||||
|
|
||||||
- name: check git status
|
|
||||||
run: git --no-pager diff --exit-code
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} CI=true go test -tags devcert -exec 'sudo' -timeout 10m -p 1 $(go list ./... | grep -v -e /management -e /signal -e /relay)
|
|
||||||
|
|
||||||
test_client_on_docker:
|
|
||||||
name: "Client (Docker) / Unit"
|
|
||||||
needs: [build-cache]
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get Go environment
|
|
||||||
id: go-env
|
|
||||||
run: |
|
|
||||||
echo "cache_dir=$(go env GOCACHE)" >> $GITHUB_OUTPUT
|
|
||||||
echo "modcache_dir=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
id: cache-restore
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{ steps.go-env.outputs.cache_dir }}
|
|
||||||
${{ steps.go-env.outputs.modcache_dir }}
|
|
||||||
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gotest-cache-
|
|
||||||
|
|
||||||
- name: Run tests in container
|
|
||||||
env:
|
|
||||||
HOST_GOCACHE: ${{ steps.go-env.outputs.cache_dir }}
|
|
||||||
HOST_GOMODCACHE: ${{ steps.go-env.outputs.modcache_dir }}
|
|
||||||
run: |
|
|
||||||
CONTAINER_GOCACHE="/root/.cache/go-build"
|
|
||||||
CONTAINER_GOMODCACHE="/go/pkg/mod"
|
|
||||||
|
|
||||||
docker run --rm \
|
|
||||||
--cap-add=NET_ADMIN \
|
|
||||||
--privileged \
|
|
||||||
-v $PWD:/app \
|
|
||||||
-w /app \
|
|
||||||
-v "${HOST_GOCACHE}:${CONTAINER_GOCACHE}" \
|
|
||||||
-v "${HOST_GOMODCACHE}:${CONTAINER_GOMODCACHE}" \
|
|
||||||
-e CGO_ENABLED=1 \
|
|
||||||
-e CI=true \
|
|
||||||
-e DOCKER_CI=true \
|
|
||||||
-e GOARCH=${GOARCH_TARGET} \
|
|
||||||
-e GOCACHE=${CONTAINER_GOCACHE} \
|
|
||||||
-e GOMODCACHE=${CONTAINER_GOMODCACHE} \
|
|
||||||
golang:1.23-alpine \
|
|
||||||
sh -c ' \
|
|
||||||
apk update; apk add --no-cache \
|
|
||||||
ca-certificates iptables ip6tables dbus dbus-dev libpcap-dev build-base; \
|
|
||||||
go test -buildvcs=false -tags devcert -v -timeout 10m -p 1 $(go list -buildvcs=false ./... | grep -v -e /management -e /signal -e /relay -e /client/ui -e /upload-server)
|
|
||||||
'
|
|
||||||
|
|
||||||
test_relay:
|
|
||||||
name: "Relay / Unit"
|
|
||||||
needs: [build-cache]
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
arch: [ '386','amd64' ]
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
run: sudo apt update && sudo apt install -y gcc-multilib g++-multilib libc6-dev-i386
|
|
||||||
|
|
||||||
- name: Get Go environment
|
|
||||||
run: |
|
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
|
||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{ env.cache }}
|
|
||||||
${{ env.modcache }}
|
|
||||||
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gotest-cache-
|
|
||||||
|
|
||||||
- name: Install modules
|
|
||||||
run: go mod tidy
|
|
||||||
|
|
||||||
- name: check git status
|
|
||||||
run: git --no-pager diff --exit-code
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
|
|
||||||
go test \
|
|
||||||
-exec 'sudo' \
|
|
||||||
-timeout 10m ./signal/...
|
|
||||||
|
|
||||||
test_signal:
|
|
||||||
name: "Signal / Unit"
|
|
||||||
needs: [build-cache]
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
arch: [ '386','amd64' ]
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
run: sudo apt update && sudo apt install -y gcc-multilib g++-multilib libc6-dev-i386
|
|
||||||
|
|
||||||
- name: Get Go environment
|
|
||||||
run: |
|
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
|
||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{ env.cache }}
|
|
||||||
${{ env.modcache }}
|
|
||||||
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gotest-cache-
|
|
||||||
|
|
||||||
- name: Install modules
|
|
||||||
run: go mod tidy
|
|
||||||
|
|
||||||
- name: check git status
|
|
||||||
run: git --no-pager diff --exit-code
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
|
|
||||||
go test \
|
|
||||||
-exec 'sudo' \
|
|
||||||
-timeout 10m ./signal/...
|
|
||||||
|
|
||||||
test_management:
|
|
||||||
name: "Management / Unit"
|
|
||||||
needs: [ build-cache ]
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
arch: [ 'amd64' ]
|
|
||||||
store: [ 'sqlite', 'postgres', 'mysql' ]
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get Go environment
|
|
||||||
run: |
|
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
|
||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{ env.cache }}
|
|
||||||
${{ env.modcache }}
|
|
||||||
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gotest-cache-
|
|
||||||
|
|
||||||
- name: Install modules
|
|
||||||
run: go mod tidy
|
|
||||||
|
|
||||||
- name: check git status
|
|
||||||
run: git --no-pager diff --exit-code
|
|
||||||
|
|
||||||
- name: Login to Docker hub
|
|
||||||
if: matrix.store == 'mysql' && (github.repository == github.head.repo.full_name || !github.head_ref)
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
|
||||||
|
|
||||||
- name: download mysql image
|
|
||||||
if: matrix.store == 'mysql'
|
|
||||||
run: docker pull mlsmaycon/warmed-mysql:8
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
|
|
||||||
NETBIRD_STORE_ENGINE=${{ matrix.store }} \
|
|
||||||
CI=true \
|
|
||||||
go test -tags=devcert \
|
|
||||||
-exec "sudo --preserve-env=CI,NETBIRD_STORE_ENGINE" \
|
|
||||||
-timeout 20m ./management/...
|
|
||||||
|
|
||||||
benchmark:
|
|
||||||
name: "Management / Benchmark"
|
|
||||||
needs: [ build-cache ]
|
|
||||||
if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
arch: [ 'amd64' ]
|
|
||||||
store: [ 'sqlite', 'postgres' ]
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get Go environment
|
|
||||||
run: |
|
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
|
||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{ env.cache }}
|
|
||||||
${{ env.modcache }}
|
|
||||||
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gotest-cache-
|
|
||||||
|
|
||||||
- name: Install modules
|
|
||||||
run: go mod tidy
|
|
||||||
|
|
||||||
- name: check git status
|
|
||||||
run: git --no-pager diff --exit-code
|
|
||||||
|
|
||||||
- name: Login to Docker hub
|
|
||||||
if: matrix.store == 'mysql' && (github.repository == github.head.repo.full_name || !github.head_ref)
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
|
||||||
|
|
||||||
- name: download mysql image
|
|
||||||
if: matrix.store == 'mysql'
|
|
||||||
run: docker pull mlsmaycon/warmed-mysql:8
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
|
|
||||||
NETBIRD_STORE_ENGINE=${{ matrix.store }} \
|
|
||||||
CI=true \
|
|
||||||
go test -tags devcert -run=^$ -bench=. \
|
|
||||||
-exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' \
|
|
||||||
-timeout 20m ./management/...
|
|
||||||
|
|
||||||
api_benchmark:
|
|
||||||
name: "Management / Benchmark (API)"
|
|
||||||
needs: [ build-cache ]
|
|
||||||
if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
arch: [ 'amd64' ]
|
|
||||||
store: [ 'sqlite', 'postgres' ]
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Create Docker network
|
|
||||||
run: docker network create promnet
|
|
||||||
|
|
||||||
- name: Start Prometheus Pushgateway
|
|
||||||
run: docker run -d --name pushgateway --network promnet -p 9091:9091 prom/pushgateway
|
|
||||||
|
|
||||||
- name: Start Prometheus (for Pushgateway forwarding)
|
|
||||||
run: |
|
|
||||||
echo '
|
|
||||||
global:
|
|
||||||
scrape_interval: 15s
|
|
||||||
scrape_configs:
|
|
||||||
- job_name: "pushgateway"
|
|
||||||
static_configs:
|
|
||||||
- targets: ["pushgateway:9091"]
|
|
||||||
remote_write:
|
|
||||||
- url: ${{ secrets.GRAFANA_URL }}
|
|
||||||
basic_auth:
|
|
||||||
username: ${{ secrets.GRAFANA_USER }}
|
|
||||||
password: ${{ secrets.GRAFANA_API_KEY }}
|
|
||||||
' > prometheus.yml
|
|
||||||
|
|
||||||
docker run -d --name prometheus --network promnet \
|
|
||||||
-v $PWD/prometheus.yml:/etc/prometheus/prometheus.yml \
|
|
||||||
-p 9090:9090 \
|
|
||||||
prom/prometheus
|
|
||||||
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get Go environment
|
|
||||||
run: |
|
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
|
||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{ env.cache }}
|
|
||||||
${{ env.modcache }}
|
|
||||||
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gotest-cache-
|
|
||||||
|
|
||||||
- name: Install modules
|
|
||||||
run: go mod tidy
|
|
||||||
|
|
||||||
- name: check git status
|
|
||||||
run: git --no-pager diff --exit-code
|
|
||||||
|
|
||||||
- name: Login to Docker hub
|
|
||||||
if: matrix.store == 'mysql' && (github.repository == github.head.repo.full_name || !github.head_ref)
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
|
||||||
|
|
||||||
- name: download mysql image
|
|
||||||
if: matrix.store == 'mysql'
|
|
||||||
run: docker pull mlsmaycon/warmed-mysql:8
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
|
|
||||||
NETBIRD_STORE_ENGINE=${{ matrix.store }} \
|
|
||||||
CI=true \
|
|
||||||
GIT_BRANCH=${{ github.ref_name }} \
|
|
||||||
go test -tags=benchmark \
|
|
||||||
-run=^$ \
|
|
||||||
-bench=. \
|
|
||||||
-exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE,GIT_BRANCH,GITHUB_RUN_ID' \
|
|
||||||
-timeout 20m ./management/...
|
|
||||||
|
|
||||||
api_integration_test:
|
|
||||||
name: "Management / Integration"
|
|
||||||
needs: [ build-cache ]
|
|
||||||
if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
arch: [ 'amd64' ]
|
|
||||||
store: [ 'sqlite', 'postgres']
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get Go environment
|
|
||||||
run: |
|
|
||||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
|
||||||
echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{ env.cache }}
|
|
||||||
${{ env.modcache }}
|
|
||||||
key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gotest-cache-
|
|
||||||
|
|
||||||
- name: Install modules
|
|
||||||
run: go mod tidy
|
|
||||||
|
|
||||||
- name: check git status
|
|
||||||
run: git --no-pager diff --exit-code
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
|
|
||||||
NETBIRD_STORE_ENGINE=${{ matrix.store }} \
|
|
||||||
CI=true \
|
|
||||||
go test -tags=integration \
|
|
||||||
-exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' \
|
|
||||||
-timeout 20m ./management/...
|
|
||||||
72
.github/workflows/golang-test-windows.yml
vendored
72
.github/workflows/golang-test-windows.yml
vendored
@@ -1,72 +0,0 @@
|
|||||||
name: "Windows"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
env:
|
|
||||||
downloadPath: '${{ github.workspace }}\temp'
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
name: "Client / Unit"
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
id: go
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: Get Go environment
|
|
||||||
run: |
|
|
||||||
echo "cache=$(go env GOCACHE)" >> $env:GITHUB_ENV
|
|
||||||
echo "modcache=$(go env GOMODCACHE)" >> $env:GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{ env.cache }}
|
|
||||||
${{ env.modcache }}
|
|
||||||
key: ${{ runner.os }}-gotest-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gotest-
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Download wintun
|
|
||||||
uses: carlosperate/download-file-action@v2
|
|
||||||
id: download-wintun
|
|
||||||
with:
|
|
||||||
file-url: https://pkgs.netbird.io/wintun/wintun-0.14.1.zip
|
|
||||||
file-name: wintun.zip
|
|
||||||
location: ${{ env.downloadPath }}
|
|
||||||
sha256: '07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51'
|
|
||||||
|
|
||||||
- name: Decompressing wintun files
|
|
||||||
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: choco install -y sysinternals --ignore-checksums
|
|
||||||
- run: choco install -y mingw
|
|
||||||
|
|
||||||
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOMODCACHE=${{ env.cache }}
|
|
||||||
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOCACHE=${{ env.modcache }}
|
|
||||||
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe mod tidy
|
|
||||||
- run: echo "files=$(go list ./... | ForEach-Object { $_ } | Where-Object { $_ -notmatch '/management' })" >> $env:GITHUB_ENV
|
|
||||||
|
|
||||||
- name: test
|
|
||||||
run: PsExec64 -s -w ${{ github.workspace }} cmd.exe /c "C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe test -tags=devcert -timeout 10m -p 1 ${{ env.files }} > test-out.txt 2>&1"
|
|
||||||
- name: test output
|
|
||||||
if: ${{ always() }}
|
|
||||||
run: Get-Content test-out.txt
|
|
||||||
58
.github/workflows/golangci-lint.yml
vendored
58
.github/workflows/golangci-lint.yml
vendored
@@ -1,58 +0,0 @@
|
|||||||
name: Lint
|
|
||||||
on: [pull_request]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
codespell:
|
|
||||||
name: codespell
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: codespell
|
|
||||||
uses: codespell-project/actions-codespell@v2
|
|
||||||
with:
|
|
||||||
ignore_words_list: erro,clienta,hastable,iif,groupd,testin,groupe
|
|
||||||
skip: go.mod,go.sum
|
|
||||||
golangci:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
|
||||||
include:
|
|
||||||
- os: macos-latest
|
|
||||||
display_name: Darwin
|
|
||||||
- os: windows-latest
|
|
||||||
display_name: Windows
|
|
||||||
- os: ubuntu-latest
|
|
||||||
display_name: Linux
|
|
||||||
name: ${{ matrix.display_name }}
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
timeout-minutes: 15
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Check for duplicate constants
|
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
run: |
|
|
||||||
! awk '/const \(/,/)/{print $0}' management/server/activity/codes.go | grep -o '= [0-9]*' | sort | uniq -d | grep .
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
cache: false
|
|
||||||
- name: Install dependencies
|
|
||||||
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
|
|
||||||
- name: golangci-lint
|
|
||||||
uses: golangci/golangci-lint-action@v4
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
args: --timeout=12m --out-format colored-line-number
|
|
||||||
37
.github/workflows/install-script-test.yml
vendored
37
.github/workflows/install-script-test.yml
vendored
@@ -1,37 +0,0 @@
|
|||||||
name: Test installation
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "release_files/install.sh"
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
test-install-script:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
max-parallel: 2
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, macos-latest]
|
|
||||||
skip_ui_mode: [true, false]
|
|
||||||
install_binary: [true, false]
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: run install script
|
|
||||||
env:
|
|
||||||
SKIP_UI_APP: ${{ matrix.skip_ui_mode }}
|
|
||||||
USE_BIN_INSTALL: ${{ matrix.install_binary }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.RO_API_CALLER_TOKEN }}
|
|
||||||
run: |
|
|
||||||
[ "$SKIP_UI_APP" == "false" ] && export XDG_CURRENT_DESKTOP="none"
|
|
||||||
cat release_files/install.sh | sh -x
|
|
||||||
|
|
||||||
- name: check cli binary
|
|
||||||
run: command -v netbird
|
|
||||||
67
.github/workflows/mobile-build-validation.yml
vendored
67
.github/workflows/mobile-build-validation.yml
vendored
@@ -1,67 +0,0 @@
|
|||||||
name: Mobile
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
android_build:
|
|
||||||
name: "Android / Build"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
- name: Setup Android SDK
|
|
||||||
uses: android-actions/setup-android@v3
|
|
||||||
with:
|
|
||||||
cmdline-tools-version: 8512546
|
|
||||||
- name: Setup Java
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
java-version: "11"
|
|
||||||
distribution: "adopt"
|
|
||||||
- name: NDK Cache
|
|
||||||
id: ndk-cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /usr/local/lib/android/sdk/ndk
|
|
||||||
key: ndk-cache-23.1.7779620
|
|
||||||
- name: Setup NDK
|
|
||||||
run: /usr/local/lib/android/sdk/cmdline-tools/7.0/bin/sdkmanager --install "ndk;23.1.7779620"
|
|
||||||
- name: install gomobile
|
|
||||||
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240404231514-09dbf07665ed
|
|
||||||
- name: gomobile init
|
|
||||||
run: gomobile init
|
|
||||||
- name: build android netbird lib
|
|
||||||
run: PATH=$PATH:$(go env GOPATH) gomobile bind -o $GITHUB_WORKSPACE/netbird.aar -javapkg=io.netbird.gomobile -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/io.netbird.client/cache/wireguard -X github.com/netbirdio/netbird/version.version=buildtest" $GITHUB_WORKSPACE/client/android
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
ANDROID_NDK_HOME: /usr/local/lib/android/sdk/ndk/23.1.7779620
|
|
||||||
ios_build:
|
|
||||||
name: "iOS / Build"
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
- name: install gomobile
|
|
||||||
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240404231514-09dbf07665ed
|
|
||||||
- name: gomobile init
|
|
||||||
run: gomobile init
|
|
||||||
- name: build iOS netbird lib
|
|
||||||
run: PATH=$PATH:$(go env GOPATH) gomobile bind -target=ios -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=buildtest" -o ./NetBirdSDK.xcframework ./client/ios/NetBirdSDK
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
34
.github/workflows/release.yml
vendored
34
.github/workflows/release.yml
vendored
@@ -55,23 +55,23 @@ jobs:
|
|||||||
run: go mod tidy
|
run: go mod tidy
|
||||||
- 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@v2
|
# uses: docker/setup-qemu-action@v2
|
||||||
- name: Set up Docker Buildx
|
# - name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
# 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@v1
|
# 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'
|
# if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v3
|
# uses: docker/login-action@v3
|
||||||
with:
|
# with:
|
||||||
registry: ghcr.io
|
# registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
# username: ${{ github.actor }}
|
||||||
password: ${{ secrets.CI_DOCKER_PUSH_GITHUB_TOKEN }}
|
# password: ${{ secrets.CI_DOCKER_PUSH_GITHUB_TOKEN }}
|
||||||
- name: Install OS build dependencies
|
- name: Install OS build dependencies
|
||||||
run: sudo apt update && sudo apt install -y -q gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu
|
run: sudo apt update && sudo apt install -y -q gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu
|
||||||
|
|
||||||
|
|||||||
22
.github/workflows/sync-main.yml
vendored
22
.github/workflows/sync-main.yml
vendored
@@ -1,22 +0,0 @@
|
|||||||
name: sync main
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
trigger_sync_main:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Trigger main branch sync
|
|
||||||
uses: benc-uk/workflow-dispatch@v1
|
|
||||||
with:
|
|
||||||
workflow: sync-main.yml
|
|
||||||
repo: ${{ secrets.UPSTREAM_REPO }}
|
|
||||||
token: ${{ secrets.NC_GITHUB_TOKEN }}
|
|
||||||
inputs: '{ "sha": "${{ github.sha }}" }'
|
|
||||||
23
.github/workflows/sync-tag.yml
vendored
23
.github/workflows/sync-tag.yml
vendored
@@ -1,23 +0,0 @@
|
|||||||
name: sync tag
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
trigger_sync_tag:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Trigger release tag sync
|
|
||||||
uses: benc-uk/workflow-dispatch@v1
|
|
||||||
with:
|
|
||||||
workflow: sync-tag.yml
|
|
||||||
ref: main
|
|
||||||
repo: ${{ secrets.UPSTREAM_REPO }}
|
|
||||||
token: ${{ secrets.NC_GITHUB_TOKEN }}
|
|
||||||
inputs: '{ "tag": "${{ github.ref_name }}" }'
|
|
||||||
310
.github/workflows/test-infrastructure-files.yml
vendored
310
.github/workflows/test-infrastructure-files.yml
vendored
@@ -1,310 +0,0 @@
|
|||||||
name: Test Infrastructure files
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'infrastructure_files/**'
|
|
||||||
- '.github/workflows/test-infrastructure-files.yml'
|
|
||||||
- 'management/cmd/**'
|
|
||||||
- 'signal/cmd/**'
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-docker-compose:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
store: [ 'sqlite', 'postgres', 'mysql' ]
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: ${{ (matrix.store == 'postgres') && 'postgres' || '' }}
|
|
||||||
env:
|
|
||||||
POSTGRES_USER: netbird
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
POSTGRES_DB: netbird
|
|
||||||
options: >-
|
|
||||||
--health-cmd pg_isready
|
|
||||||
--health-interval 10s
|
|
||||||
--health-timeout 5s
|
|
||||||
ports:
|
|
||||||
- 5432:5432
|
|
||||||
mysql:
|
|
||||||
image: ${{ (matrix.store == 'mysql') && 'mysql' || '' }}
|
|
||||||
env:
|
|
||||||
MYSQL_USER: netbird
|
|
||||||
MYSQL_PASSWORD: mysql
|
|
||||||
MYSQL_ROOT_PASSWORD: mysqlroot
|
|
||||||
MYSQL_DATABASE: netbird
|
|
||||||
options: >-
|
|
||||||
--health-cmd "mysqladmin ping --silent"
|
|
||||||
--health-interval 10s
|
|
||||||
--health-timeout 5s
|
|
||||||
ports:
|
|
||||||
- 3306:3306
|
|
||||||
steps:
|
|
||||||
- name: Set Database Connection String
|
|
||||||
run: |
|
|
||||||
if [ "${{ matrix.store }}" == "postgres" ]; then
|
|
||||||
echo "NETBIRD_STORE_ENGINE_POSTGRES_DSN=host=$(hostname -I | awk '{print $1}') user=netbird password=postgres dbname=netbird port=5432" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "NETBIRD_STORE_ENGINE_POSTGRES_DSN==" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
if [ "${{ matrix.store }}" == "mysql" ]; then
|
|
||||||
echo "NETBIRD_STORE_ENGINE_MYSQL_DSN=netbird:mysql@tcp($(hostname -I | awk '{print $1}'):3306)/netbird" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "NETBIRD_STORE_ENGINE_MYSQL_DSN==" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Install jq
|
|
||||||
run: sudo apt-get install -y jq
|
|
||||||
|
|
||||||
- name: Install curl
|
|
||||||
run: sudo apt-get install -y curl
|
|
||||||
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.23.x"
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: cp setup.env
|
|
||||||
run: cp infrastructure_files/tests/setup.env infrastructure_files/
|
|
||||||
|
|
||||||
- name: run configure
|
|
||||||
working-directory: infrastructure_files
|
|
||||||
run: bash -x configure.sh
|
|
||||||
env:
|
|
||||||
CI_NETBIRD_DOMAIN: localhost
|
|
||||||
CI_NETBIRD_AUTH_CLIENT_ID: testing.client.id
|
|
||||||
CI_NETBIRD_AUTH_CLIENT_SECRET: testing.client.secret
|
|
||||||
CI_NETBIRD_AUTH_AUDIENCE: testing.ci
|
|
||||||
CI_NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT: https://example.eu.auth0.com/.well-known/openid-configuration
|
|
||||||
CI_NETBIRD_USE_AUTH0: true
|
|
||||||
CI_NETBIRD_MGMT_IDP: "none"
|
|
||||||
CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id
|
|
||||||
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
|
||||||
CI_NETBIRD_AUTH_SUPPORTED_SCOPES: "openid profile email offline_access api email_verified"
|
|
||||||
CI_NETBIRD_STORE_CONFIG_ENGINE: ${{ matrix.store }}
|
|
||||||
NETBIRD_STORE_ENGINE_POSTGRES_DSN: ${{ env.NETBIRD_STORE_ENGINE_POSTGRES_DSN }}
|
|
||||||
NETBIRD_STORE_ENGINE_MYSQL_DSN: ${{ env.NETBIRD_STORE_ENGINE_MYSQL_DSN }}
|
|
||||||
CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH: false
|
|
||||||
|
|
||||||
- name: check values
|
|
||||||
working-directory: infrastructure_files/artifacts
|
|
||||||
env:
|
|
||||||
CI_NETBIRD_DOMAIN: localhost
|
|
||||||
CI_NETBIRD_AUTH_CLIENT_ID: testing.client.id
|
|
||||||
CI_NETBIRD_AUTH_CLIENT_SECRET: testing.client.secret
|
|
||||||
CI_NETBIRD_AUTH_AUDIENCE: testing.ci
|
|
||||||
CI_NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT: https://example.eu.auth0.com/.well-known/openid-configuration
|
|
||||||
CI_NETBIRD_USE_AUTH0: true
|
|
||||||
CI_NETBIRD_AUTH_SUPPORTED_SCOPES: "openid profile email offline_access api email_verified"
|
|
||||||
CI_NETBIRD_AUTH_AUTHORITY: https://example.eu.auth0.com/
|
|
||||||
CI_NETBIRD_AUTH_JWT_CERTS: https://example.eu.auth0.com/.well-known/jwks.json
|
|
||||||
CI_NETBIRD_AUTH_TOKEN_ENDPOINT: https://example.eu.auth0.com/oauth/token
|
|
||||||
CI_NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT: https://example.eu.auth0.com/oauth/device/code
|
|
||||||
CI_NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT: https://example.eu.auth0.com/authorize
|
|
||||||
CI_NETBIRD_AUTH_REDIRECT_URI: "/peers"
|
|
||||||
CI_NETBIRD_TOKEN_SOURCE: "idToken"
|
|
||||||
CI_NETBIRD_AUTH_USER_ID_CLAIM: "email"
|
|
||||||
CI_NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE: "super"
|
|
||||||
CI_NETBIRD_AUTH_DEVICE_AUTH_SCOPE: "openid email"
|
|
||||||
CI_NETBIRD_MGMT_IDP: "none"
|
|
||||||
CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id
|
|
||||||
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
|
||||||
CI_NETBIRD_SIGNAL_PORT: 12345
|
|
||||||
CI_NETBIRD_STORE_CONFIG_ENGINE: ${{ matrix.store }}
|
|
||||||
NETBIRD_STORE_ENGINE_POSTGRES_DSN: '${{ env.NETBIRD_STORE_ENGINE_POSTGRES_DSN }}$'
|
|
||||||
NETBIRD_STORE_ENGINE_MYSQL_DSN: '${{ env.NETBIRD_STORE_ENGINE_MYSQL_DSN }}$'
|
|
||||||
CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH: false
|
|
||||||
CI_NETBIRD_TURN_EXTERNAL_IP: "1.2.3.4"
|
|
||||||
|
|
||||||
run: |
|
|
||||||
set -x
|
|
||||||
grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
|
||||||
grep AUTH_CLIENT_SECRET docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_SECRET
|
|
||||||
grep AUTH_AUTHORITY docker-compose.yml | grep $CI_NETBIRD_AUTH_AUTHORITY
|
|
||||||
grep AUTH_AUDIENCE docker-compose.yml | grep $CI_NETBIRD_AUTH_AUDIENCE
|
|
||||||
grep AUTH_SUPPORTED_SCOPES docker-compose.yml | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES"
|
|
||||||
grep USE_AUTH0 docker-compose.yml | grep $CI_NETBIRD_USE_AUTH0
|
|
||||||
grep NETBIRD_MGMT_API_ENDPOINT docker-compose.yml | grep "$CI_NETBIRD_DOMAIN:33073"
|
|
||||||
grep AUTH_REDIRECT_URI docker-compose.yml | grep $CI_NETBIRD_AUTH_REDIRECT_URI
|
|
||||||
grep AUTH_SILENT_REDIRECT_URI docker-compose.yml | egrep 'AUTH_SILENT_REDIRECT_URI=$'
|
|
||||||
grep $CI_NETBIRD_SIGNAL_PORT docker-compose.yml | grep ':80'
|
|
||||||
grep LETSENCRYPT_DOMAIN docker-compose.yml | egrep 'LETSENCRYPT_DOMAIN=$'
|
|
||||||
grep NETBIRD_TOKEN_SOURCE docker-compose.yml | grep $CI_NETBIRD_TOKEN_SOURCE
|
|
||||||
grep AuthUserIDClaim management.json | grep $CI_NETBIRD_AUTH_USER_ID_CLAIM
|
|
||||||
grep -A 3 DeviceAuthorizationFlow management.json | grep -A 1 ProviderConfig | grep Audience | grep $CI_NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE
|
|
||||||
grep -A 3 DeviceAuthorizationFlow management.json | grep -A 1 ProviderConfig | grep Audience | grep $CI_NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE
|
|
||||||
grep Engine management.json | grep "$CI_NETBIRD_STORE_CONFIG_ENGINE"
|
|
||||||
grep IdpSignKeyRefreshEnabled management.json | grep "$CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH"
|
|
||||||
grep UseIDToken management.json | grep false
|
|
||||||
grep -A 1 IdpManagerConfig management.json | grep ManagerType | grep $CI_NETBIRD_MGMT_IDP
|
|
||||||
grep -A 3 IdpManagerConfig management.json | grep -A 1 ClientConfig | grep Issuer | grep $CI_NETBIRD_AUTH_AUTHORITY
|
|
||||||
grep -A 4 IdpManagerConfig management.json | grep -A 2 ClientConfig | grep TokenEndpoint | grep $CI_NETBIRD_AUTH_TOKEN_ENDPOINT
|
|
||||||
grep -A 5 IdpManagerConfig management.json | grep -A 3 ClientConfig | grep ClientID | grep $CI_NETBIRD_IDP_MGMT_CLIENT_ID
|
|
||||||
grep -A 6 IdpManagerConfig management.json | grep -A 4 ClientConfig | grep ClientSecret | grep $CI_NETBIRD_IDP_MGMT_CLIENT_SECRET
|
|
||||||
grep -A 7 IdpManagerConfig management.json | grep -A 5 ClientConfig | grep GrantType | grep client_credentials
|
|
||||||
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep Audience | grep $CI_NETBIRD_AUTH_AUDIENCE
|
|
||||||
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep ClientID | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
|
||||||
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep ClientSecret | grep $CI_NETBIRD_AUTH_CLIENT_SECRET
|
|
||||||
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep AuthorizationEndpoint | grep $CI_NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT
|
|
||||||
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep TokenEndpoint | grep $CI_NETBIRD_AUTH_TOKEN_ENDPOINT
|
|
||||||
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep Scope | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES"
|
|
||||||
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep -A 3 RedirectURLs | grep "http://localhost:53000"
|
|
||||||
grep "external-ip" turnserver.conf | grep $CI_NETBIRD_TURN_EXTERNAL_IP
|
|
||||||
grep "NETBIRD_STORE_ENGINE_MYSQL_DSN=$NETBIRD_STORE_ENGINE_MYSQL_DSN" docker-compose.yml
|
|
||||||
grep NETBIRD_STORE_ENGINE_POSTGRES_DSN docker-compose.yml | egrep "$NETBIRD_STORE_ENGINE_POSTGRES_DSN"
|
|
||||||
# check relay values
|
|
||||||
grep "NB_EXPOSED_ADDRESS=rels://$CI_NETBIRD_DOMAIN:33445" docker-compose.yml
|
|
||||||
grep "NB_LISTEN_ADDRESS=:33445" docker-compose.yml
|
|
||||||
grep '33445:33445' docker-compose.yml
|
|
||||||
grep -A 10 'relay:' docker-compose.yml | egrep 'NB_AUTH_SECRET=.+$'
|
|
||||||
grep -A 7 Relay management.json | grep "rels://$CI_NETBIRD_DOMAIN:33445"
|
|
||||||
grep -A 7 Relay management.json | egrep '"Secret": ".+"'
|
|
||||||
grep DisablePromptLogin management.json | grep 'true'
|
|
||||||
grep LoginFlag management.json | grep 0
|
|
||||||
|
|
||||||
- name: Install modules
|
|
||||||
run: go mod tidy
|
|
||||||
|
|
||||||
- name: check git status
|
|
||||||
run: git --no-pager diff --exit-code
|
|
||||||
|
|
||||||
- name: Build management binary
|
|
||||||
working-directory: management
|
|
||||||
run: CGO_ENABLED=1 go build -o netbird-mgmt main.go
|
|
||||||
|
|
||||||
- name: Build management docker image
|
|
||||||
working-directory: management
|
|
||||||
run: |
|
|
||||||
docker build -t netbirdio/management:latest .
|
|
||||||
|
|
||||||
- name: Build signal binary
|
|
||||||
working-directory: signal
|
|
||||||
run: CGO_ENABLED=0 go build -o netbird-signal main.go
|
|
||||||
|
|
||||||
- name: Build signal docker image
|
|
||||||
working-directory: signal
|
|
||||||
run: |
|
|
||||||
docker build -t netbirdio/signal:latest .
|
|
||||||
|
|
||||||
- name: Build relay binary
|
|
||||||
working-directory: relay
|
|
||||||
run: CGO_ENABLED=0 go build -o netbird-relay main.go
|
|
||||||
|
|
||||||
- name: Build relay docker image
|
|
||||||
working-directory: relay
|
|
||||||
run: |
|
|
||||||
docker build -t netbirdio/relay:latest .
|
|
||||||
|
|
||||||
- name: run docker compose up
|
|
||||||
working-directory: infrastructure_files/artifacts
|
|
||||||
run: |
|
|
||||||
docker compose up -d
|
|
||||||
sleep 5
|
|
||||||
docker compose ps
|
|
||||||
docker compose logs --tail=20
|
|
||||||
|
|
||||||
- name: test running containers
|
|
||||||
run: |
|
|
||||||
count=$(docker compose ps --format json | jq '. | select(.Name | contains("artifacts")) | .State' | grep -c running)
|
|
||||||
test $count -eq 5 || docker compose logs
|
|
||||||
working-directory: infrastructure_files/artifacts
|
|
||||||
|
|
||||||
- name: test geolocation databases
|
|
||||||
working-directory: infrastructure_files/artifacts
|
|
||||||
run: |
|
|
||||||
sleep 30
|
|
||||||
docker compose exec management ls -l /var/lib/netbird/ | grep -i GeoLite2-City_[0-9]*.mmdb
|
|
||||||
docker compose exec management ls -l /var/lib/netbird/ | grep -i geonames_[0-9]*.db
|
|
||||||
|
|
||||||
test-getting-started-script:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Install jq
|
|
||||||
run: sudo apt-get install -y jq
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: run script with Zitadel PostgreSQL
|
|
||||||
run: NETBIRD_DOMAIN=use-ip bash -x infrastructure_files/getting-started-with-zitadel.sh
|
|
||||||
|
|
||||||
- name: test Caddy file gen postgres
|
|
||||||
run: test -f Caddyfile
|
|
||||||
|
|
||||||
- name: test docker-compose file gen postgres
|
|
||||||
run: test -f docker-compose.yml
|
|
||||||
|
|
||||||
- name: test management.json file gen postgres
|
|
||||||
run: test -f management.json
|
|
||||||
|
|
||||||
- name: test turnserver.conf file gen postgres
|
|
||||||
run: |
|
|
||||||
set -x
|
|
||||||
test -f turnserver.conf
|
|
||||||
grep external-ip turnserver.conf
|
|
||||||
|
|
||||||
- name: test zitadel.env file gen postgres
|
|
||||||
run: test -f zitadel.env
|
|
||||||
|
|
||||||
- name: test dashboard.env file gen postgres
|
|
||||||
run: test -f dashboard.env
|
|
||||||
|
|
||||||
- name: test relay.env file gen postgres
|
|
||||||
run: test -f relay.env
|
|
||||||
|
|
||||||
- name: test zdb.env file gen postgres
|
|
||||||
run: test -f zdb.env
|
|
||||||
|
|
||||||
- name: Postgres run cleanup
|
|
||||||
run: |
|
|
||||||
docker compose down --volumes --rmi all
|
|
||||||
rm -rf docker-compose.yml Caddyfile zitadel.env dashboard.env machinekey/zitadel-admin-sa.token turnserver.conf management.json zdb.env
|
|
||||||
|
|
||||||
- name: run script with Zitadel CockroachDB
|
|
||||||
run: bash -x infrastructure_files/getting-started-with-zitadel.sh
|
|
||||||
env:
|
|
||||||
NETBIRD_DOMAIN: use-ip
|
|
||||||
ZITADEL_DATABASE: cockroach
|
|
||||||
|
|
||||||
- name: test Caddy file gen CockroachDB
|
|
||||||
run: test -f Caddyfile
|
|
||||||
|
|
||||||
- name: test docker-compose file gen CockroachDB
|
|
||||||
run: test -f docker-compose.yml
|
|
||||||
|
|
||||||
- name: test management.json file gen CockroachDB
|
|
||||||
run: test -f management.json
|
|
||||||
|
|
||||||
- name: test turnserver.conf file gen CockroachDB
|
|
||||||
run: |
|
|
||||||
set -x
|
|
||||||
test -f turnserver.conf
|
|
||||||
grep external-ip turnserver.conf
|
|
||||||
|
|
||||||
- name: test zitadel.env file gen CockroachDB
|
|
||||||
run: test -f zitadel.env
|
|
||||||
|
|
||||||
- name: test dashboard.env file gen CockroachDB
|
|
||||||
run: test -f dashboard.env
|
|
||||||
|
|
||||||
- name: test relay.env file gen CockroachDB
|
|
||||||
run: test -f relay.env
|
|
||||||
22
.github/workflows/update-docs.yml
vendored
22
.github/workflows/update-docs.yml
vendored
@@ -1,22 +0,0 @@
|
|||||||
name: update docs
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
paths:
|
|
||||||
- 'management/server/http/api/openapi.yml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
trigger_docs_api_update:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
steps:
|
|
||||||
- name: Trigger API pages generation
|
|
||||||
uses: benc-uk/workflow-dispatch@v1
|
|
||||||
with:
|
|
||||||
workflow: generate api pages
|
|
||||||
repo: netbirdio/docs
|
|
||||||
ref: "refs/heads/main"
|
|
||||||
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
|
||||||
inputs: '{ "tag": "${{ github.ref }}" }'
|
|
||||||
1060
.goreleaser.yaml
1060
.goreleaser.yaml
File diff suppressed because it is too large
Load Diff
@@ -79,19 +79,19 @@ nfpms:
|
|||||||
dependencies:
|
dependencies:
|
||||||
- netbird
|
- netbird
|
||||||
|
|
||||||
uploads:
|
# uploads:
|
||||||
- name: debian
|
# - name: debian
|
||||||
ids:
|
# ids:
|
||||||
- netbird-ui-deb
|
# - netbird-ui-deb
|
||||||
mode: archive
|
# mode: archive
|
||||||
target: https://pkgs.wiretrustee.com/debian/pool/{{ .ArtifactName }};deb.distribution=stable;deb.component=main;deb.architecture={{ if .Arm }}armhf{{ else }}{{ .Arch }}{{ end }};deb.package=
|
# target: https://pkgs.wiretrustee.com/debian/pool/{{ .ArtifactName }};deb.distribution=stable;deb.component=main;deb.architecture={{ if .Arm }}armhf{{ else }}{{ .Arch }}{{ end }};deb.package=
|
||||||
username: dev@wiretrustee.com
|
# username: dev@wiretrustee.com
|
||||||
method: PUT
|
# method: PUT
|
||||||
|
|
||||||
- name: yum
|
# - name: yum
|
||||||
ids:
|
# ids:
|
||||||
- netbird-ui-rpm
|
# - netbird-ui-rpm
|
||||||
mode: archive
|
# mode: archive
|
||||||
target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }}
|
# target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }}
|
||||||
username: dev@wiretrustee.com
|
# username: dev@wiretrustee.com
|
||||||
method: PUT
|
# method: PUT
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
FROM alpine:3.21.3
|
FROM alpine:3.21.3
|
||||||
# iproute2: busybox doesn't display ip rules properly
|
# iproute2: busybox doesn't display ip rules properly
|
||||||
RUN apk add --no-cache ca-certificates ip6tables iproute2 iptables
|
RUN apk add --no-cache ca-certificates ip6tables iproute2 iptables
|
||||||
|
|
||||||
|
ARG NETBIRD_BINARY=netbird
|
||||||
|
COPY ${NETBIRD_BINARY} /usr/local/bin/netbird
|
||||||
|
|
||||||
ENV NB_FOREGROUND_MODE=true
|
ENV NB_FOREGROUND_MODE=true
|
||||||
ENTRYPOINT [ "/usr/local/bin/netbird","up"]
|
ENTRYPOINT [ "/usr/local/bin/netbird","up"]
|
||||||
COPY netbird /usr/local/bin/netbird
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
FROM alpine:3.21.0
|
FROM alpine:3.21.0
|
||||||
|
|
||||||
COPY netbird /usr/local/bin/netbird
|
ARG NETBIRD_BINARY=netbird
|
||||||
|
COPY ${NETBIRD_BINARY} /usr/local/bin/netbird
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates \
|
RUN apk add --no-cache ca-certificates \
|
||||||
&& adduser -D -h /var/lib/netbird netbird
|
&& adduser -D -h /var/lib/netbird netbird
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Preferences export a subset of the internal config for gomobile
|
// Preferences exports a subset of the internal config for gomobile
|
||||||
type Preferences struct {
|
type Preferences struct {
|
||||||
configInput internal.ConfigInput
|
configInput internal.ConfigInput
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPreferences create new Preferences instance
|
// NewPreferences creates a new Preferences instance
|
||||||
func NewPreferences(configPath string) *Preferences {
|
func NewPreferences(configPath string) *Preferences {
|
||||||
ci := internal.ConfigInput{
|
ci := internal.ConfigInput{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
@@ -17,7 +17,7 @@ func NewPreferences(configPath string) *Preferences {
|
|||||||
return &Preferences{ci}
|
return &Preferences{ci}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetManagementURL read url from config file
|
// GetManagementURL reads URL from config file
|
||||||
func (p *Preferences) GetManagementURL() (string, error) {
|
func (p *Preferences) GetManagementURL() (string, error) {
|
||||||
if p.configInput.ManagementURL != "" {
|
if p.configInput.ManagementURL != "" {
|
||||||
return p.configInput.ManagementURL, nil
|
return p.configInput.ManagementURL, nil
|
||||||
@@ -30,12 +30,12 @@ func (p *Preferences) GetManagementURL() (string, error) {
|
|||||||
return cfg.ManagementURL.String(), err
|
return cfg.ManagementURL.String(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetManagementURL store the given url and wait for commit
|
// SetManagementURL stores the given URL and waits for commit
|
||||||
func (p *Preferences) SetManagementURL(url string) {
|
func (p *Preferences) SetManagementURL(url string) {
|
||||||
p.configInput.ManagementURL = url
|
p.configInput.ManagementURL = url
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAdminURL read url from config file
|
// GetAdminURL reads URL from config file
|
||||||
func (p *Preferences) GetAdminURL() (string, error) {
|
func (p *Preferences) GetAdminURL() (string, error) {
|
||||||
if p.configInput.AdminURL != "" {
|
if p.configInput.AdminURL != "" {
|
||||||
return p.configInput.AdminURL, nil
|
return p.configInput.AdminURL, nil
|
||||||
@@ -48,12 +48,12 @@ func (p *Preferences) GetAdminURL() (string, error) {
|
|||||||
return cfg.AdminURL.String(), err
|
return cfg.AdminURL.String(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAdminURL store the given url and wait for commit
|
// SetAdminURL stores the given URL and waits for commit
|
||||||
func (p *Preferences) SetAdminURL(url string) {
|
func (p *Preferences) SetAdminURL(url string) {
|
||||||
p.configInput.AdminURL = url
|
p.configInput.AdminURL = url
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPreSharedKey read preshared key from config file
|
// GetPreSharedKey reads pre-shared key from config file
|
||||||
func (p *Preferences) GetPreSharedKey() (string, error) {
|
func (p *Preferences) GetPreSharedKey() (string, error) {
|
||||||
if p.configInput.PreSharedKey != nil {
|
if p.configInput.PreSharedKey != nil {
|
||||||
return *p.configInput.PreSharedKey, nil
|
return *p.configInput.PreSharedKey, nil
|
||||||
@@ -66,17 +66,17 @@ func (p *Preferences) GetPreSharedKey() (string, error) {
|
|||||||
return cfg.PreSharedKey, err
|
return cfg.PreSharedKey, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPreSharedKey store the given key and wait for commit
|
// SetPreSharedKey stores the given key and waits for commit
|
||||||
func (p *Preferences) SetPreSharedKey(key string) {
|
func (p *Preferences) SetPreSharedKey(key string) {
|
||||||
p.configInput.PreSharedKey = &key
|
p.configInput.PreSharedKey = &key
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRosenpassEnabled store if rosenpass is enabled
|
// SetRosenpassEnabled stores whether Rosenpass is enabled
|
||||||
func (p *Preferences) SetRosenpassEnabled(enabled bool) {
|
func (p *Preferences) SetRosenpassEnabled(enabled bool) {
|
||||||
p.configInput.RosenpassEnabled = &enabled
|
p.configInput.RosenpassEnabled = &enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRosenpassEnabled read rosenpass enabled from config file
|
// GetRosenpassEnabled reads Rosenpass enabled status from config file
|
||||||
func (p *Preferences) GetRosenpassEnabled() (bool, error) {
|
func (p *Preferences) GetRosenpassEnabled() (bool, error) {
|
||||||
if p.configInput.RosenpassEnabled != nil {
|
if p.configInput.RosenpassEnabled != nil {
|
||||||
return *p.configInput.RosenpassEnabled, nil
|
return *p.configInput.RosenpassEnabled, nil
|
||||||
@@ -89,12 +89,12 @@ func (p *Preferences) GetRosenpassEnabled() (bool, error) {
|
|||||||
return cfg.RosenpassEnabled, err
|
return cfg.RosenpassEnabled, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRosenpassPermissive store the given permissive and wait for commit
|
// SetRosenpassPermissive stores the given permissive setting and waits for commit
|
||||||
func (p *Preferences) SetRosenpassPermissive(permissive bool) {
|
func (p *Preferences) SetRosenpassPermissive(permissive bool) {
|
||||||
p.configInput.RosenpassPermissive = &permissive
|
p.configInput.RosenpassPermissive = &permissive
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRosenpassPermissive read rosenpass permissive from config file
|
// GetRosenpassPermissive reads Rosenpass permissive setting from config file
|
||||||
func (p *Preferences) GetRosenpassPermissive() (bool, error) {
|
func (p *Preferences) GetRosenpassPermissive() (bool, error) {
|
||||||
if p.configInput.RosenpassPermissive != nil {
|
if p.configInput.RosenpassPermissive != nil {
|
||||||
return *p.configInput.RosenpassPermissive, nil
|
return *p.configInput.RosenpassPermissive, nil
|
||||||
@@ -107,7 +107,119 @@ func (p *Preferences) GetRosenpassPermissive() (bool, error) {
|
|||||||
return cfg.RosenpassPermissive, err
|
return cfg.RosenpassPermissive, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit write out the changes into config file
|
// GetDisableClientRoutes reads disable client routes setting from config file
|
||||||
|
func (p *Preferences) GetDisableClientRoutes() (bool, error) {
|
||||||
|
if p.configInput.DisableClientRoutes != nil {
|
||||||
|
return *p.configInput.DisableClientRoutes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return cfg.DisableClientRoutes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDisableClientRoutes stores the given value and waits for commit
|
||||||
|
func (p *Preferences) SetDisableClientRoutes(disable bool) {
|
||||||
|
p.configInput.DisableClientRoutes = &disable
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDisableServerRoutes reads disable server routes setting from config file
|
||||||
|
func (p *Preferences) GetDisableServerRoutes() (bool, error) {
|
||||||
|
if p.configInput.DisableServerRoutes != nil {
|
||||||
|
return *p.configInput.DisableServerRoutes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return cfg.DisableServerRoutes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDisableServerRoutes stores the given value and waits for commit
|
||||||
|
func (p *Preferences) SetDisableServerRoutes(disable bool) {
|
||||||
|
p.configInput.DisableServerRoutes = &disable
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDisableDNS reads disable DNS setting from config file
|
||||||
|
func (p *Preferences) GetDisableDNS() (bool, error) {
|
||||||
|
if p.configInput.DisableDNS != nil {
|
||||||
|
return *p.configInput.DisableDNS, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return cfg.DisableDNS, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDisableDNS stores the given value and waits for commit
|
||||||
|
func (p *Preferences) SetDisableDNS(disable bool) {
|
||||||
|
p.configInput.DisableDNS = &disable
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDisableFirewall reads disable firewall setting from config file
|
||||||
|
func (p *Preferences) GetDisableFirewall() (bool, error) {
|
||||||
|
if p.configInput.DisableFirewall != nil {
|
||||||
|
return *p.configInput.DisableFirewall, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return cfg.DisableFirewall, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDisableFirewall stores the given value and waits for commit
|
||||||
|
func (p *Preferences) SetDisableFirewall(disable bool) {
|
||||||
|
p.configInput.DisableFirewall = &disable
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServerSSHAllowed reads server SSH allowed setting from config file
|
||||||
|
func (p *Preferences) GetServerSSHAllowed() (bool, error) {
|
||||||
|
if p.configInput.ServerSSHAllowed != nil {
|
||||||
|
return *p.configInput.ServerSSHAllowed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if cfg.ServerSSHAllowed == nil {
|
||||||
|
// Default to false for security on Android
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return *cfg.ServerSSHAllowed, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetServerSSHAllowed stores the given value and waits for commit
|
||||||
|
func (p *Preferences) SetServerSSHAllowed(allowed bool) {
|
||||||
|
p.configInput.ServerSSHAllowed = &allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockInbound reads block inbound setting from config file
|
||||||
|
func (p *Preferences) GetBlockInbound() (bool, error) {
|
||||||
|
if p.configInput.BlockInbound != nil {
|
||||||
|
return *p.configInput.BlockInbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return cfg.BlockInbound, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBlockInbound stores the given value and waits for commit
|
||||||
|
func (p *Preferences) SetBlockInbound(block bool) {
|
||||||
|
p.configInput.BlockInbound = &block
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit writes out the changes to the config file
|
||||||
func (p *Preferences) Commit() error {
|
func (p *Preferences) Commit() error {
|
||||||
_, err := internal.UpdateOrCreateConfig(p.configInput)
|
_, err := internal.UpdateOrCreateConfig(p.configInput)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type WGTunDevice struct {
|
|||||||
mtu int
|
mtu int
|
||||||
iceBind *bind.ICEBind
|
iceBind *bind.ICEBind
|
||||||
tunAdapter TunAdapter
|
tunAdapter TunAdapter
|
||||||
|
disableDNS bool
|
||||||
|
|
||||||
name string
|
name string
|
||||||
device *device.Device
|
device *device.Device
|
||||||
@@ -32,7 +33,7 @@ type WGTunDevice struct {
|
|||||||
configurer WGConfigurer
|
configurer WGConfigurer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTunDevice(address wgaddr.Address, port int, key string, mtu int, iceBind *bind.ICEBind, tunAdapter TunAdapter) *WGTunDevice {
|
func NewTunDevice(address wgaddr.Address, port int, key string, mtu int, iceBind *bind.ICEBind, tunAdapter TunAdapter, disableDNS bool) *WGTunDevice {
|
||||||
return &WGTunDevice{
|
return &WGTunDevice{
|
||||||
address: address,
|
address: address,
|
||||||
port: port,
|
port: port,
|
||||||
@@ -40,6 +41,7 @@ func NewTunDevice(address wgaddr.Address, port int, key string, mtu int, iceBind
|
|||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
iceBind: iceBind,
|
iceBind: iceBind,
|
||||||
tunAdapter: tunAdapter,
|
tunAdapter: tunAdapter,
|
||||||
|
disableDNS: disableDNS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +51,13 @@ func (t *WGTunDevice) Create(routes []string, dns string, searchDomains []string
|
|||||||
routesString := routesToString(routes)
|
routesString := routesToString(routes)
|
||||||
searchDomainsToString := searchDomainsToString(searchDomains)
|
searchDomainsToString := searchDomainsToString(searchDomains)
|
||||||
|
|
||||||
|
// Skip DNS configuration when DisableDNS is enabled
|
||||||
|
if t.disableDNS {
|
||||||
|
log.Info("DNS is disabled, skipping DNS and search domain configuration")
|
||||||
|
dns = ""
|
||||||
|
searchDomainsToString = ""
|
||||||
|
}
|
||||||
|
|
||||||
fd, err := t.tunAdapter.ConfigureInterface(t.address.String(), t.mtu, dns, searchDomainsToString, routesString)
|
fd, err := t.tunAdapter.ConfigureInterface(t.address.String(), t.mtu, dns, searchDomainsToString, routesString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to create Android interface: %s", err)
|
log.Errorf("failed to create Android interface: %s", err)
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ type WGIFaceOpts struct {
|
|||||||
MobileArgs *device.MobileIFaceArguments
|
MobileArgs *device.MobileIFaceArguments
|
||||||
TransportNet transport.Net
|
TransportNet transport.Net
|
||||||
FilterFn bind.FilterFn
|
FilterFn bind.FilterFn
|
||||||
|
DisableDNS bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// WGIface represents an interface instance
|
// WGIface represents an interface instance
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) {
|
|||||||
|
|
||||||
wgIFace := &WGIface{
|
wgIFace := &WGIface{
|
||||||
userspaceBind: true,
|
userspaceBind: true,
|
||||||
tun: device.NewTunDevice(wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, opts.MobileArgs.TunAdapter),
|
tun: device.NewTunDevice(wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, opts.MobileArgs.TunAdapter, opts.DisableDNS),
|
||||||
wgProxyFactory: wgproxy.NewUSPFactory(iceBind),
|
wgProxyFactory: wgproxy.NewUSPFactory(iceBind),
|
||||||
}
|
}
|
||||||
return wgIFace, nil
|
return wgIFace, nil
|
||||||
|
|||||||
@@ -398,11 +398,15 @@ func (d *DefaultManager) squashAcceptRules(
|
|||||||
//
|
//
|
||||||
// We zeroed this to notify squash function that this protocol can't be squashed.
|
// We zeroed this to notify squash function that this protocol can't be squashed.
|
||||||
addRuleToCalculationMap := func(i int, r *mgmProto.FirewallRule, protocols map[mgmProto.RuleProtocol]*protoMatch) {
|
addRuleToCalculationMap := func(i int, r *mgmProto.FirewallRule, protocols map[mgmProto.RuleProtocol]*protoMatch) {
|
||||||
drop := r.Action == mgmProto.RuleAction_DROP || r.Port != ""
|
hasPortRestrictions := r.Action == mgmProto.RuleAction_DROP ||
|
||||||
if drop {
|
r.Port != "" || !portInfoEmpty(r.PortInfo)
|
||||||
|
|
||||||
|
if hasPortRestrictions {
|
||||||
|
// Don't squash rules with port restrictions
|
||||||
protocols[r.Protocol] = &protoMatch{ips: map[string]int{}}
|
protocols[r.Protocol] = &protoMatch{ips: map[string]int{}}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := protocols[r.Protocol]; !ok {
|
if _, ok := protocols[r.Protocol]; !ok {
|
||||||
protocols[r.Protocol] = &protoMatch{
|
protocols[r.Protocol] = &protoMatch{
|
||||||
ips: map[string]int{},
|
ips: map[string]int{},
|
||||||
|
|||||||
@@ -330,6 +330,434 @@ func TestDefaultManagerSquashRulesNoAffect(t *testing.T) {
|
|||||||
assert.Equal(t, len(networkMap.FirewallRules), len(rules))
|
assert.Equal(t, len(networkMap.FirewallRules), len(rules))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefaultManagerSquashRulesWithPortRestrictions(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
rules []*mgmProto.FirewallRule
|
||||||
|
expectedCount int
|
||||||
|
description string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should not squash rules with port ranges",
|
||||||
|
rules: []*mgmProto.FirewallRule{
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.1",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
PortInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Range_{
|
||||||
|
Range: &mgmProto.PortInfo_Range{
|
||||||
|
Start: 8080,
|
||||||
|
End: 8090,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.2",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
PortInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Range_{
|
||||||
|
Range: &mgmProto.PortInfo_Range{
|
||||||
|
Start: 8080,
|
||||||
|
End: 8090,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.3",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
PortInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Range_{
|
||||||
|
Range: &mgmProto.PortInfo_Range{
|
||||||
|
Start: 8080,
|
||||||
|
End: 8090,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.4",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
PortInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Range_{
|
||||||
|
Range: &mgmProto.PortInfo_Range{
|
||||||
|
Start: 8080,
|
||||||
|
End: 8090,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedCount: 4,
|
||||||
|
description: "Rules with port ranges should not be squashed even if they cover all peers",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should not squash rules with specific ports",
|
||||||
|
rules: []*mgmProto.FirewallRule{
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.1",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
PortInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Port{
|
||||||
|
Port: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.2",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
PortInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Port{
|
||||||
|
Port: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.3",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
PortInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Port{
|
||||||
|
Port: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.4",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
PortInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Port{
|
||||||
|
Port: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedCount: 4,
|
||||||
|
description: "Rules with specific ports should not be squashed even if they cover all peers",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should not squash rules with legacy port field",
|
||||||
|
rules: []*mgmProto.FirewallRule{
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.1",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
Port: "443",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.2",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
Port: "443",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.3",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
Port: "443",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.4",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
Port: "443",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedCount: 4,
|
||||||
|
description: "Rules with legacy port field should not be squashed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should not squash rules with DROP action",
|
||||||
|
rules: []*mgmProto.FirewallRule{
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.1",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_DROP,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.2",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_DROP,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.3",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_DROP,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.4",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_DROP,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedCount: 4,
|
||||||
|
description: "Rules with DROP action should not be squashed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should squash rules without port restrictions",
|
||||||
|
rules: []*mgmProto.FirewallRule{
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.1",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.2",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.3",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.4",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedCount: 1,
|
||||||
|
description: "Rules without port restrictions should be squashed into a single 0.0.0.0 rule",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mixed rules should not squash protocol with port restrictions",
|
||||||
|
rules: []*mgmProto.FirewallRule{
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.1",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.2",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
PortInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Port{
|
||||||
|
Port: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.3",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.4",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedCount: 4,
|
||||||
|
description: "TCP should not be squashed because one rule has port restrictions",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should squash UDP but not TCP when TCP has port restrictions",
|
||||||
|
rules: []*mgmProto.FirewallRule{
|
||||||
|
// TCP rules with port restrictions - should NOT be squashed
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.1",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
Port: "443",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.2",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
Port: "443",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.3",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
Port: "443",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.4",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_TCP,
|
||||||
|
Port: "443",
|
||||||
|
},
|
||||||
|
// UDP rules without port restrictions - SHOULD be squashed
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.1",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_UDP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.2",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_UDP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.3",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_UDP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "10.93.0.4",
|
||||||
|
Direction: mgmProto.RuleDirection_IN,
|
||||||
|
Action: mgmProto.RuleAction_ACCEPT,
|
||||||
|
Protocol: mgmProto.RuleProtocol_UDP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedCount: 5, // 4 TCP rules + 1 squashed UDP rule (0.0.0.0)
|
||||||
|
description: "UDP should be squashed to 0.0.0.0 rule, but TCP should remain as individual rules due to port restrictions",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
networkMap := &mgmProto.NetworkMap{
|
||||||
|
RemotePeers: []*mgmProto.RemotePeerConfig{
|
||||||
|
{AllowedIps: []string{"10.93.0.1"}},
|
||||||
|
{AllowedIps: []string{"10.93.0.2"}},
|
||||||
|
{AllowedIps: []string{"10.93.0.3"}},
|
||||||
|
{AllowedIps: []string{"10.93.0.4"}},
|
||||||
|
},
|
||||||
|
FirewallRules: tt.rules,
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := &DefaultManager{}
|
||||||
|
rules, _ := manager.squashAcceptRules(networkMap)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.expectedCount, len(rules), tt.description)
|
||||||
|
|
||||||
|
// For squashed rules, verify we get the expected 0.0.0.0 rule
|
||||||
|
if tt.expectedCount == 1 {
|
||||||
|
assert.Equal(t, "0.0.0.0", rules[0].PeerIP)
|
||||||
|
assert.Equal(t, mgmProto.RuleDirection_IN, rules[0].Direction)
|
||||||
|
assert.Equal(t, mgmProto.RuleAction_ACCEPT, rules[0].Action)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPortInfoEmpty(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
portInfo *mgmProto.PortInfo
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil PortInfo should be empty",
|
||||||
|
portInfo: nil,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PortInfo with zero port should be empty",
|
||||||
|
portInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Port{
|
||||||
|
Port: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PortInfo with valid port should not be empty",
|
||||||
|
portInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Port{
|
||||||
|
Port: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PortInfo with nil range should be empty",
|
||||||
|
portInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Range_{
|
||||||
|
Range: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PortInfo with zero start range should be empty",
|
||||||
|
portInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Range_{
|
||||||
|
Range: &mgmProto.PortInfo_Range{
|
||||||
|
Start: 0,
|
||||||
|
End: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PortInfo with zero end range should be empty",
|
||||||
|
portInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Range_{
|
||||||
|
Range: &mgmProto.PortInfo_Range{
|
||||||
|
Start: 80,
|
||||||
|
End: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PortInfo with valid range should not be empty",
|
||||||
|
portInfo: &mgmProto.PortInfo{
|
||||||
|
PortSelection: &mgmProto.PortInfo_Range_{
|
||||||
|
Range: &mgmProto.PortInfo_Range{
|
||||||
|
Start: 8080,
|
||||||
|
End: 8090,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := portInfoEmpty(tt.portInfo)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDefaultManagerEnableSSHRules(t *testing.T) {
|
func TestDefaultManagerEnableSSHRules(t *testing.T) {
|
||||||
networkMap := &mgmProto.NetworkMap{
|
networkMap := &mgmProto.NetworkMap{
|
||||||
PeerConfig: &mgmProto.PeerConfig{
|
PeerConfig: &mgmProto.PeerConfig{
|
||||||
|
|||||||
@@ -223,6 +223,8 @@ func createNewConfig(input ConfigInput) (*Config, error) {
|
|||||||
config := &Config{
|
config := &Config{
|
||||||
// defaults to false only for new (post 0.26) configurations
|
// defaults to false only for new (post 0.26) configurations
|
||||||
ServerSSHAllowed: util.False(),
|
ServerSSHAllowed: util.False(),
|
||||||
|
// default to disabling server routes on Android for security
|
||||||
|
DisableServerRoutes: runtime.GOOS == "android",
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := config.apply(input); err != nil {
|
if _, err := config.apply(input); err != nil {
|
||||||
@@ -416,9 +418,15 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
|
|||||||
config.ServerSSHAllowed = input.ServerSSHAllowed
|
config.ServerSSHAllowed = input.ServerSSHAllowed
|
||||||
updated = true
|
updated = true
|
||||||
} else if config.ServerSSHAllowed == nil {
|
} else if config.ServerSSHAllowed == nil {
|
||||||
|
if runtime.GOOS == "android" {
|
||||||
|
// default to disabled SSH on Android for security
|
||||||
|
log.Infof("setting SSH server to false by default on Android")
|
||||||
|
config.ServerSSHAllowed = util.False()
|
||||||
|
} else {
|
||||||
// enables SSH for configs from old versions to preserve backwards compatibility
|
// enables SSH for configs from old versions to preserve backwards compatibility
|
||||||
log.Infof("falling back to enabled SSH server for pre-existing configuration")
|
log.Infof("falling back to enabled SSH server for pre-existing configuration")
|
||||||
config.ServerSSHAllowed = util.True()
|
config.ServerSSHAllowed = util.True()
|
||||||
|
}
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ func (e *ConnMgr) AddPeerConn(ctx context.Context, peerKey string, conn *peer.Co
|
|||||||
PeerConnID: conn.ConnID(),
|
PeerConnID: conn.ConnID(),
|
||||||
Log: conn.Log,
|
Log: conn.Log,
|
||||||
}
|
}
|
||||||
excluded, err := e.lazyConnMgr.AddPeer(lazyPeerCfg)
|
excluded, err := e.lazyConnMgr.AddPeer(e.lazyCtx, lazyPeerCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Log.Errorf("failed to add peer to lazyconn manager: %v", err)
|
conn.Log.Errorf("failed to add peer to lazyconn manager: %v", err)
|
||||||
if err := conn.Open(ctx); err != nil {
|
if err := conn.Open(ctx); err != nil {
|
||||||
|
|||||||
@@ -1527,6 +1527,7 @@ func (e *Engine) newWgIface() (*iface.WGIface, error) {
|
|||||||
MTU: iface.DefaultMTU,
|
MTU: iface.DefaultMTU,
|
||||||
TransportNet: transportNet,
|
TransportNet: transportNet,
|
||||||
FilterFn: e.addrViaRoutes,
|
FilterFn: e.addrViaRoutes,
|
||||||
|
DisableDNS: e.config.DisableDNS,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
|
|||||||
@@ -68,3 +68,8 @@ func (i *Monitor) PauseTimer() {
|
|||||||
func (i *Monitor) ResetTimer() {
|
func (i *Monitor) ResetTimer() {
|
||||||
i.timer.Reset(i.inactivityThreshold)
|
i.timer.Reset(i.inactivityThreshold)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Monitor) ResetMonitor(ctx context.Context, timeoutChan chan peer.ConnID) {
|
||||||
|
i.Stop()
|
||||||
|
go i.Start(ctx, timeoutChan)
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ type Manager struct {
|
|||||||
// Route HA group management
|
// Route HA group management
|
||||||
peerToHAGroups map[string][]route.HAUniqueID // peer ID -> HA groups they belong to
|
peerToHAGroups map[string][]route.HAUniqueID // peer ID -> HA groups they belong to
|
||||||
haGroupToPeers map[route.HAUniqueID][]string // HA group -> peer IDs in the group
|
haGroupToPeers map[route.HAUniqueID][]string // HA group -> peer IDs in the group
|
||||||
routesMu sync.RWMutex // protects route mappings
|
routesMu sync.RWMutex
|
||||||
|
|
||||||
onInactive chan peerid.ConnID
|
onInactive chan peerid.ConnID
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ func (m *Manager) Start(ctx context.Context) {
|
|||||||
case peerConnID := <-m.activityManager.OnActivityChan:
|
case peerConnID := <-m.activityManager.OnActivityChan:
|
||||||
m.onPeerActivity(ctx, peerConnID)
|
m.onPeerActivity(ctx, peerConnID)
|
||||||
case peerConnID := <-m.onInactive:
|
case peerConnID := <-m.onInactive:
|
||||||
m.onPeerInactivityTimedOut(peerConnID)
|
m.onPeerInactivityTimedOut(ctx, peerConnID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +197,7 @@ func (m *Manager) ExcludePeer(ctx context.Context, peerConfigs []lazyconn.PeerCo
|
|||||||
return added
|
return added
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) AddPeer(peerCfg lazyconn.PeerConfig) (bool, error) {
|
func (m *Manager) AddPeer(ctx context.Context, peerCfg lazyconn.PeerConfig) (bool, error) {
|
||||||
m.managedPeersMu.Lock()
|
m.managedPeersMu.Lock()
|
||||||
defer m.managedPeersMu.Unlock()
|
defer m.managedPeersMu.Unlock()
|
||||||
|
|
||||||
@@ -225,6 +225,13 @@ func (m *Manager) AddPeer(peerCfg lazyconn.PeerConfig) (bool, error) {
|
|||||||
peerCfg: &peerCfg,
|
peerCfg: &peerCfg,
|
||||||
expectedWatcher: watcherActivity,
|
expectedWatcher: watcherActivity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this peer should be activated because its HA group peers are active
|
||||||
|
if group, ok := m.shouldActivateNewPeer(peerCfg.PublicKey); ok {
|
||||||
|
peerCfg.Log.Debugf("peer belongs to active HA group %s, will activate immediately", group)
|
||||||
|
m.activateNewPeerInActiveGroup(ctx, peerCfg)
|
||||||
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,26 +322,29 @@ func (m *Manager) activateSinglePeer(ctx context.Context, cfg *lazyconn.PeerConf
|
|||||||
|
|
||||||
// activateHAGroupPeers activates all peers in HA groups that the given peer belongs to
|
// activateHAGroupPeers activates all peers in HA groups that the given peer belongs to
|
||||||
func (m *Manager) activateHAGroupPeers(ctx context.Context, triggerPeerID string) {
|
func (m *Manager) activateHAGroupPeers(ctx context.Context, triggerPeerID string) {
|
||||||
|
var peersToActivate []string
|
||||||
|
|
||||||
m.routesMu.RLock()
|
m.routesMu.RLock()
|
||||||
haGroups := m.peerToHAGroups[triggerPeerID]
|
haGroups := m.peerToHAGroups[triggerPeerID]
|
||||||
m.routesMu.RUnlock()
|
|
||||||
|
|
||||||
if len(haGroups) == 0 {
|
if len(haGroups) == 0 {
|
||||||
|
m.routesMu.RUnlock()
|
||||||
log.Debugf("peer %s is not part of any HA groups", triggerPeerID)
|
log.Debugf("peer %s is not part of any HA groups", triggerPeerID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
activatedCount := 0
|
|
||||||
for _, haGroup := range haGroups {
|
for _, haGroup := range haGroups {
|
||||||
m.routesMu.RLock()
|
|
||||||
peers := m.haGroupToPeers[haGroup]
|
peers := m.haGroupToPeers[haGroup]
|
||||||
|
for _, peerID := range peers {
|
||||||
|
if peerID != triggerPeerID {
|
||||||
|
peersToActivate = append(peersToActivate, peerID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
m.routesMu.RUnlock()
|
m.routesMu.RUnlock()
|
||||||
|
|
||||||
for _, peerID := range peers {
|
activatedCount := 0
|
||||||
if peerID == triggerPeerID {
|
for _, peerID := range peersToActivate {
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, mp := m.getPeerForActivation(peerID)
|
cfg, mp := m.getPeerForActivation(peerID)
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
continue
|
continue
|
||||||
@@ -342,11 +352,10 @@ func (m *Manager) activateHAGroupPeers(ctx context.Context, triggerPeerID string
|
|||||||
|
|
||||||
if m.activateSinglePeer(ctx, cfg, mp) {
|
if m.activateSinglePeer(ctx, cfg, mp) {
|
||||||
activatedCount++
|
activatedCount++
|
||||||
cfg.Log.Infof("activated peer as part of HA group %s (triggered by %s)", haGroup, triggerPeerID)
|
cfg.Log.Infof("activated peer as part of HA group (triggered by %s)", triggerPeerID)
|
||||||
m.peerStore.PeerConnOpen(m.engineCtx, cfg.PublicKey)
|
m.peerStore.PeerConnOpen(m.engineCtx, cfg.PublicKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if activatedCount > 0 {
|
if activatedCount > 0 {
|
||||||
log.Infof("activated %d additional peers in HA groups for peer %s (groups: %v)",
|
log.Infof("activated %d additional peers in HA groups for peer %s (groups: %v)",
|
||||||
@@ -354,6 +363,51 @@ func (m *Manager) activateHAGroupPeers(ctx context.Context, triggerPeerID string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shouldActivateNewPeer checks if a newly added peer should be activated
|
||||||
|
// because other peers in its HA groups are already active
|
||||||
|
func (m *Manager) shouldActivateNewPeer(peerID string) (route.HAUniqueID, bool) {
|
||||||
|
m.routesMu.RLock()
|
||||||
|
defer m.routesMu.RUnlock()
|
||||||
|
|
||||||
|
haGroups := m.peerToHAGroups[peerID]
|
||||||
|
if len(haGroups) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, haGroup := range haGroups {
|
||||||
|
peers := m.haGroupToPeers[haGroup]
|
||||||
|
for _, groupPeerID := range peers {
|
||||||
|
if groupPeerID == peerID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, ok := m.managedPeers[groupPeerID]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if mp, ok := m.managedPeersByConnID[cfg.PeerConnID]; ok && mp.expectedWatcher == watcherInactivity {
|
||||||
|
return haGroup, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// activateNewPeerInActiveGroup activates a newly added peer that should be active due to HA group
|
||||||
|
func (m *Manager) activateNewPeerInActiveGroup(ctx context.Context, peerCfg lazyconn.PeerConfig) {
|
||||||
|
mp, ok := m.managedPeersByConnID[peerCfg.PeerConnID]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.activateSinglePeer(ctx, &peerCfg, mp) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerCfg.Log.Infof("activated newly added peer due to active HA group peers")
|
||||||
|
m.peerStore.PeerConnOpen(m.engineCtx, peerCfg.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) addActivePeer(ctx context.Context, peerCfg lazyconn.PeerConfig) error {
|
func (m *Manager) addActivePeer(ctx context.Context, peerCfg lazyconn.PeerConfig) error {
|
||||||
if _, ok := m.managedPeers[peerCfg.PublicKey]; ok {
|
if _, ok := m.managedPeers[peerCfg.PublicKey]; ok {
|
||||||
peerCfg.Log.Warnf("peer already managed")
|
peerCfg.Log.Warnf("peer already managed")
|
||||||
@@ -415,6 +469,48 @@ func (m *Manager) close() {
|
|||||||
log.Infof("lazy connection manager closed")
|
log.Infof("lazy connection manager closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shouldDeferIdleForHA checks if peer should stay connected due to HA group requirements
|
||||||
|
func (m *Manager) shouldDeferIdleForHA(peerID string) bool {
|
||||||
|
m.routesMu.RLock()
|
||||||
|
defer m.routesMu.RUnlock()
|
||||||
|
|
||||||
|
haGroups := m.peerToHAGroups[peerID]
|
||||||
|
if len(haGroups) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, haGroup := range haGroups {
|
||||||
|
groupPeers := m.haGroupToPeers[haGroup]
|
||||||
|
|
||||||
|
for _, groupPeerID := range groupPeers {
|
||||||
|
if groupPeerID == peerID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, ok := m.managedPeers[groupPeerID]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
groupMp, ok := m.managedPeersByConnID[cfg.PeerConnID]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupMp.expectedWatcher != watcherInactivity {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other member is still connected, defer idle
|
||||||
|
if peer, ok := m.peerStore.PeerConn(groupPeerID); ok && peer.IsConnected() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) onPeerActivity(ctx context.Context, peerConnID peerid.ConnID) {
|
func (m *Manager) onPeerActivity(ctx context.Context, peerConnID peerid.ConnID) {
|
||||||
m.managedPeersMu.Lock()
|
m.managedPeersMu.Lock()
|
||||||
defer m.managedPeersMu.Unlock()
|
defer m.managedPeersMu.Unlock()
|
||||||
@@ -441,7 +537,7 @@ func (m *Manager) onPeerActivity(ctx context.Context, peerConnID peerid.ConnID)
|
|||||||
m.peerStore.PeerConnOpen(m.engineCtx, mp.peerCfg.PublicKey)
|
m.peerStore.PeerConnOpen(m.engineCtx, mp.peerCfg.PublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) onPeerInactivityTimedOut(peerConnID peerid.ConnID) {
|
func (m *Manager) onPeerInactivityTimedOut(ctx context.Context, peerConnID peerid.ConnID) {
|
||||||
m.managedPeersMu.Lock()
|
m.managedPeersMu.Lock()
|
||||||
defer m.managedPeersMu.Unlock()
|
defer m.managedPeersMu.Unlock()
|
||||||
|
|
||||||
@@ -456,6 +552,17 @@ func (m *Manager) onPeerInactivityTimedOut(peerConnID peerid.ConnID) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m.shouldDeferIdleForHA(mp.peerCfg.PublicKey) {
|
||||||
|
iw, ok := m.inactivityMonitors[peerConnID]
|
||||||
|
if ok {
|
||||||
|
mp.peerCfg.Log.Debugf("resetting inactivity timer due to HA group requirements")
|
||||||
|
iw.ResetMonitor(ctx, m.onInactive)
|
||||||
|
} else {
|
||||||
|
mp.peerCfg.Log.Errorf("inactivity monitor not found for HA defer reset")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
mp.peerCfg.Log.Infof("connection timed out")
|
mp.peerCfg.Log.Infof("connection timed out")
|
||||||
|
|
||||||
// this is blocking operation, potentially can be optimized
|
// this is blocking operation, potentially can be optimized
|
||||||
@@ -489,7 +596,7 @@ func (m *Manager) onPeerConnected(peerConnID peerid.ConnID) {
|
|||||||
|
|
||||||
iw, ok := m.inactivityMonitors[mp.peerCfg.PeerConnID]
|
iw, ok := m.inactivityMonitors[mp.peerCfg.PeerConnID]
|
||||||
if !ok {
|
if !ok {
|
||||||
mp.peerCfg.Log.Errorf("inactivity monitor not found for peer")
|
mp.peerCfg.Log.Warnf("inactivity monitor not found for peer")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -317,12 +317,12 @@ func (conn *Conn) WgConfig() WgConfig {
|
|||||||
return conn.config.WgConfig
|
return conn.config.WgConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsConnected unit tests only
|
// IsConnected returns true if the peer is connected
|
||||||
// refactor unit test to use status recorder use refactor status recorded to manage connection status in peer.Conn
|
|
||||||
func (conn *Conn) IsConnected() bool {
|
func (conn *Conn) IsConnected() bool {
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
defer conn.mu.Unlock()
|
defer conn.mu.Unlock()
|
||||||
return conn.currentConnPriority != conntype.None
|
|
||||||
|
return conn.evalStatus() == StatusConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) GetKey() string {
|
func (conn *Conn) GetKey() string {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
// MockManager is the mock instance of a route manager
|
// MockManager is the mock instance of a route manager
|
||||||
type MockManager struct {
|
type MockManager struct {
|
||||||
ClassifyRoutesFunc func(routes []*route.Route) (map[route.ID]*route.Route, route.HAMap)
|
ClassifyRoutesFunc func(routes []*route.Route) (map[route.ID]*route.Route, route.HAMap)
|
||||||
UpdateRoutesFunc func (updateSerial uint64, serverRoutes map[route.ID]*route.Route, clientRoutes route.HAMap, useNewDNSRoute bool) error
|
UpdateRoutesFunc func(updateSerial uint64, serverRoutes map[route.ID]*route.Route, clientRoutes route.HAMap, useNewDNSRoute bool) error
|
||||||
TriggerSelectionFunc func(haMap route.HAMap)
|
TriggerSelectionFunc func(haMap route.HAMap)
|
||||||
GetRouteSelectorFunc func() *routeselector.RouteSelector
|
GetRouteSelectorFunc func() *routeselector.RouteSelector
|
||||||
GetClientRoutesFunc func() route.HAMap
|
GetClientRoutesFunc func() route.HAMap
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
|
|||||||
func (n *Notifier) SetInitialClientRoutes(clientRoutes []*route.Route) {
|
func (n *Notifier) SetInitialClientRoutes(clientRoutes []*route.Route) {
|
||||||
nets := make([]string, 0)
|
nets := make([]string, 0)
|
||||||
for _, r := range clientRoutes {
|
for _, r := range clientRoutes {
|
||||||
// filter out domain routes
|
|
||||||
if r.IsDynamic() {
|
if r.IsDynamic() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -46,30 +45,27 @@ func (n *Notifier) OnNewRoutes(idMap route.HAMap) {
|
|||||||
if runtime.GOOS != "android" {
|
if runtime.GOOS != "android" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
newNets := make([]string, 0)
|
|
||||||
|
var newNets []string
|
||||||
for _, routes := range idMap {
|
for _, routes := range idMap {
|
||||||
for _, r := range routes {
|
for _, r := range routes {
|
||||||
|
if r.IsDynamic() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
newNets = append(newNets, r.Network.String())
|
newNets = append(newNets, r.Network.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(newNets)
|
sort.Strings(newNets)
|
||||||
switch runtime.GOOS {
|
|
||||||
case "android":
|
|
||||||
if !n.hasDiff(n.initialRouteRanges, newNets) {
|
if !n.hasDiff(n.initialRouteRanges, newNets) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
if !n.hasDiff(n.routeRanges, newNets) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n.routeRanges = newNets
|
n.routeRanges = newNets
|
||||||
|
|
||||||
n.notify()
|
n.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnNewPrefixes is called from iOS only
|
||||||
func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
|
func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
|
||||||
newNets := make([]string, 0)
|
newNets := make([]string, 0)
|
||||||
for _, prefix := range prefixes {
|
for _, prefix := range prefixes {
|
||||||
@@ -77,19 +73,11 @@ func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(newNets)
|
sort.Strings(newNets)
|
||||||
switch runtime.GOOS {
|
|
||||||
case "android":
|
|
||||||
if !n.hasDiff(n.initialRouteRanges, newNets) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if !n.hasDiff(n.routeRanges, newNets) {
|
if !n.hasDiff(n.routeRanges, newNets) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
n.routeRanges = newNets
|
n.routeRanges = newNets
|
||||||
|
|
||||||
n.notify()
|
n.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,28 +46,40 @@
|
|||||||
<ComponentRef Id="NetbirdFiles" />
|
<ComponentRef Id="NetbirdFiles" />
|
||||||
</ComponentGroup>
|
</ComponentGroup>
|
||||||
|
|
||||||
<Property Id="cmd" Value="cmd.exe"/>
|
|
||||||
|
|
||||||
<CustomAction Id="KillDaemon"
|
<CustomAction Id="KillDaemon"
|
||||||
ExeCommand='/c "taskkill /im netbird.exe"'
|
BinaryRef="WixCA"
|
||||||
|
DllEntry="WixQuietExec64"
|
||||||
Execute="deferred"
|
Execute="deferred"
|
||||||
Property="cmd"
|
|
||||||
Impersonate="no"
|
Impersonate="no"
|
||||||
Return="ignore"
|
Return="ignore"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CustomAction Id="KillUI"
|
<CustomAction Id="KillUI"
|
||||||
ExeCommand='/c "taskkill /im netbird-ui.exe"'
|
BinaryRef="WixCA"
|
||||||
|
DllEntry="WixQuietExec64"
|
||||||
Execute="deferred"
|
Execute="deferred"
|
||||||
Property="cmd"
|
|
||||||
Impersonate="no"
|
Impersonate="no"
|
||||||
Return="ignore"
|
Return="ignore"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CustomAction Id="SetKillDaemonCommand"
|
||||||
|
Property="KillDaemon"
|
||||||
|
Value="taskkill /f /im netbird.exe"
|
||||||
|
Execute="immediate"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CustomAction Id="SetKillUICommand"
|
||||||
|
Property="KillUI"
|
||||||
|
Value="taskkill /f /im netbird-ui.exe"
|
||||||
|
Execute="immediate"
|
||||||
|
/>
|
||||||
|
|
||||||
<InstallExecuteSequence>
|
<InstallExecuteSequence>
|
||||||
<!-- For Uninstallation -->
|
<!-- For Uninstallation -->
|
||||||
|
<Custom Action="SetKillDaemonCommand" Before="KillDaemon" Condition="Installed"/>
|
||||||
<Custom Action="KillDaemon" Before="RemoveFiles" Condition="Installed"/>
|
<Custom Action="KillDaemon" Before="RemoveFiles" Condition="Installed"/>
|
||||||
<Custom Action="KillUI" After="KillDaemon" Condition="Installed"/>
|
<Custom Action="SetKillUICommand" After="KillDaemon" Condition="Installed"/>
|
||||||
|
<Custom Action="KillUI" After="SetKillUICommand" Condition="Installed"/>
|
||||||
</InstallExecuteSequence>
|
</InstallExecuteSequence>
|
||||||
|
|
||||||
<!-- Icons -->
|
<!-- Icons -->
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ func newServiceClient(addr string, logFile string, a fyne.App, showSettings bool
|
|||||||
|
|
||||||
showAdvancedSettings: showSettings,
|
showAdvancedSettings: showSettings,
|
||||||
showNetworks: showNetworks,
|
showNetworks: showNetworks,
|
||||||
update: version.NewUpdate(),
|
update: version.NewUpdate("nb/client-ui"),
|
||||||
}
|
}
|
||||||
|
|
||||||
s.eventHandler = newEventHandler(s)
|
s.eventHandler = newEventHandler(s)
|
||||||
@@ -879,7 +879,7 @@ func (s *serviceClient) onUpdateAvailable() {
|
|||||||
func (s *serviceClient) onSessionExpire() {
|
func (s *serviceClient) onSessionExpire() {
|
||||||
s.sendNotification = true
|
s.sendNotification = true
|
||||||
if s.sendNotification {
|
if s.sendNotification {
|
||||||
s.eventHandler.runSelfCommand("login-url", "true")
|
s.eventHandler.runSelfCommand(s.ctx, "login-url", "true")
|
||||||
s.sendNotification = false
|
s.sendNotification = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -992,21 +992,6 @@ func (s *serviceClient) restartClient(loginRequest *proto.LoginRequest) error {
|
|||||||
// showLoginURL creates a borderless window styled like a pop-up in the top-right corner using s.wLoginURL.
|
// showLoginURL creates a borderless window styled like a pop-up in the top-right corner using s.wLoginURL.
|
||||||
func (s *serviceClient) showLoginURL() {
|
func (s *serviceClient) showLoginURL() {
|
||||||
|
|
||||||
resp, err := s.login(false)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to fetch login URL: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
verificationURL := resp.VerificationURIComplete
|
|
||||||
if verificationURL == "" {
|
|
||||||
verificationURL = resp.VerificationURI
|
|
||||||
}
|
|
||||||
|
|
||||||
if verificationURL == "" {
|
|
||||||
log.Error("no verification URL provided in the login response")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resIcon := fyne.NewStaticResource("netbird.png", iconAbout)
|
resIcon := fyne.NewStaticResource("netbird.png", iconAbout)
|
||||||
|
|
||||||
if s.wLoginURL == nil {
|
if s.wLoginURL == nil {
|
||||||
@@ -1025,6 +1010,21 @@ func (s *serviceClient) showLoginURL() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resp, err := s.login(false)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to fetch login URL: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
verificationURL := resp.VerificationURIComplete
|
||||||
|
if verificationURL == "" {
|
||||||
|
verificationURL = resp.VerificationURI
|
||||||
|
}
|
||||||
|
|
||||||
|
if verificationURL == "" {
|
||||||
|
log.Error("no verification URL provided in the login response")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := openURL(verificationURL); err != nil {
|
if err := openURL(verificationURL); err != nil {
|
||||||
log.Errorf("failed to open login URL: %v", err)
|
log.Errorf("failed to open login URL: %v", err)
|
||||||
return
|
return
|
||||||
@@ -1038,7 +1038,19 @@ func (s *serviceClient) showLoginURL() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
label.SetText("Re-authentication successful.\nReconnecting")
|
label.SetText("Re-authentication successful.\nReconnecting")
|
||||||
time.Sleep(300 * time.Millisecond)
|
status, err := conn.Status(s.ctx, &proto.StatusRequest{})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("get service status: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.Status == string(internal.StatusConnected) {
|
||||||
|
label.SetText("Already connected.\nClosing this window.")
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
s.wLoginURL.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
_, err = conn.Up(s.ctx, &proto.UpRequest{})
|
_, err = conn.Up(s.ctx, &proto.UpRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
label.SetText("Reconnecting failed, please create \na debug bundle in the settings and contact support.")
|
label.SetText("Reconnecting failed, please create \na debug bundle in the settings and contact support.")
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ func (h *eventHandler) handleAdvancedSettingsClick() {
|
|||||||
go func() {
|
go func() {
|
||||||
defer h.client.mAdvancedSettings.Enable()
|
defer h.client.mAdvancedSettings.Enable()
|
||||||
defer h.client.getSrvConfig()
|
defer h.client.getSrvConfig()
|
||||||
h.runSelfCommand("settings", "true")
|
h.runSelfCommand(h.client.ctx, "settings", "true")
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ func (h *eventHandler) handleCreateDebugBundleClick() {
|
|||||||
h.client.mCreateDebugBundle.Disable()
|
h.client.mCreateDebugBundle.Disable()
|
||||||
go func() {
|
go func() {
|
||||||
defer h.client.mCreateDebugBundle.Enable()
|
defer h.client.mCreateDebugBundle.Enable()
|
||||||
h.runSelfCommand("debug", "true")
|
h.runSelfCommand(h.client.ctx, "debug", "true")
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ func (h *eventHandler) handleNetworksClick() {
|
|||||||
h.client.mNetworks.Disable()
|
h.client.mNetworks.Disable()
|
||||||
go func() {
|
go func() {
|
||||||
defer h.client.mNetworks.Enable()
|
defer h.client.mNetworks.Enable()
|
||||||
h.runSelfCommand("networks", "true")
|
h.runSelfCommand(h.client.ctx, "networks", "true")
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,14 +172,14 @@ func (h *eventHandler) updateConfigWithErr() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *eventHandler) runSelfCommand(command, arg string) {
|
func (h *eventHandler) runSelfCommand(ctx context.Context, command, arg string) {
|
||||||
proc, err := os.Executable()
|
proc, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error getting executable path: %v", err)
|
log.Errorf("error getting executable path: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(proc,
|
cmd := exec.CommandContext(ctx, proc,
|
||||||
fmt.Sprintf("--%s=%s", command, arg),
|
fmt.Sprintf("--%s=%s", command, arg),
|
||||||
fmt.Sprintf("--daemon-addr=%s", h.client.addr),
|
fmt.Sprintf("--daemon-addr=%s", h.client.addr),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -357,6 +357,13 @@ var (
|
|||||||
log.WithContext(ctx).Infof("running HTTP server and gRPC server on the same port: %s", listener.Addr().String())
|
log.WithContext(ctx).Infof("running HTTP server and gRPC server on the same port: %s", listener.Addr().String())
|
||||||
serveGRPCWithHTTP(ctx, listener, rootHandler, tlsEnabled)
|
serveGRPCWithHTTP(ctx, listener, rootHandler, tlsEnabled)
|
||||||
|
|
||||||
|
update := version.NewUpdate("nb/management")
|
||||||
|
update.SetDaemonVersion(version.NetbirdVersion())
|
||||||
|
update.SetOnUpdateListener(func() {
|
||||||
|
log.WithContext(ctx).Infof("your management version, \"%s\", is outdated, a new management version is available. Learn more here: https://github.com/netbirdio/netbird/releases", version.NetbirdVersion())
|
||||||
|
})
|
||||||
|
defer update.StopWatch()
|
||||||
|
|
||||||
SetupCloseHandler()
|
SetupCloseHandler()
|
||||||
|
|
||||||
<-stopCh
|
<-stopCh
|
||||||
|
|||||||
@@ -1853,20 +1853,23 @@ func (am *DefaultAccountManager) GetOrCreateAccountByPrivateDomain(ctx context.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) UpdateToPrimaryAccount(ctx context.Context, accountId string) (*types.Account, error) {
|
func (am *DefaultAccountManager) UpdateToPrimaryAccount(ctx context.Context, accountId string) (*types.Account, error) {
|
||||||
account, err := am.Store.GetAccount(ctx, accountId)
|
var account *types.Account
|
||||||
|
err := am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||||
|
var err error
|
||||||
|
account, err = transaction.GetAccount(ctx, accountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if account.IsDomainPrimaryAccount {
|
if account.IsDomainPrimaryAccount {
|
||||||
return account, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
existingPrimaryAccountID, err := am.Store.GetAccountIDByPrivateDomain(ctx, store.LockingStrengthShare, account.Domain)
|
existingPrimaryAccountID, err := transaction.GetAccountIDByPrivateDomain(ctx, store.LockingStrengthShare, account.Domain)
|
||||||
|
|
||||||
// error is not a not found error
|
// error is not a not found error
|
||||||
if handleNotFound(err) != nil {
|
if handleNotFound(err) != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// a primary account already exists for this private domain
|
// a primary account already exists for this private domain
|
||||||
@@ -1875,16 +1878,22 @@ func (am *DefaultAccountManager) UpdateToPrimaryAccount(ctx context.Context, acc
|
|||||||
"accountId": accountId,
|
"accountId": accountId,
|
||||||
"existingAccountId": existingPrimaryAccountID,
|
"existingAccountId": existingPrimaryAccountID,
|
||||||
}).Errorf("cannot update account to primary, another account already exists as primary for the same domain")
|
}).Errorf("cannot update account to primary, another account already exists as primary for the same domain")
|
||||||
return nil, status.Errorf(status.Internal, "cannot update account to primary")
|
return status.Errorf(status.Internal, "cannot update account to primary")
|
||||||
}
|
}
|
||||||
|
|
||||||
account.IsDomainPrimaryAccount = true
|
account.IsDomainPrimaryAccount = true
|
||||||
|
|
||||||
if err := am.Store.SaveAccount(ctx, account); err != nil {
|
if err := transaction.SaveAccount(ctx, account); err != nil {
|
||||||
log.WithContext(ctx).WithFields(log.Fields{
|
log.WithContext(ctx).WithFields(log.Fields{
|
||||||
"accountId": accountId,
|
"accountId": accountId,
|
||||||
}).Errorf("failed to update account to primary: %v", err)
|
}).Errorf("failed to update account to primary: %v", err)
|
||||||
return nil, status.Errorf(status.Internal, "failed to update account to primary")
|
return status.Errorf(status.Internal, "failed to update account to primary")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return account, nil
|
return account, nil
|
||||||
|
|||||||
@@ -664,15 +664,6 @@ func areGroupChangesAffectPeers(ctx context.Context, transaction store.Store, ac
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) anyGroupHasPeers(account *types.Account, groupIDs []string) bool {
|
|
||||||
for _, groupID := range groupIDs {
|
|
||||||
if group, exists := account.Groups[groupID]; exists && group.HasPeers() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// anyGroupHasPeersOrResources checks if any of the given groups in the account have peers or resources.
|
// anyGroupHasPeersOrResources checks if any of the given groups in the account have peers or resources.
|
||||||
func anyGroupHasPeersOrResources(ctx context.Context, transaction store.Store, accountID string, groupIDs []string) (bool, error) {
|
func anyGroupHasPeersOrResources(ctx context.Context, transaction store.Store, accountID string, groupIDs []string) (bool, error) {
|
||||||
groups, err := transaction.GetGroupsByIDs(ctx, store.LockingStrengthShare, accountID, groupIDs)
|
groups, err := transaction.GetGroupsByIDs(ctx, store.LockingStrengthShare, accountID, groupIDs)
|
||||||
|
|||||||
@@ -426,6 +426,10 @@ components:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
example: "stage-host-1"
|
example: "stage-host-1"
|
||||||
|
ephemeral:
|
||||||
|
description: Indicates whether the peer is ephemeral or not
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
required:
|
required:
|
||||||
- city_name
|
- city_name
|
||||||
- connected
|
- connected
|
||||||
@@ -450,6 +454,7 @@ components:
|
|||||||
- approval_required
|
- approval_required
|
||||||
- serial_number
|
- serial_number
|
||||||
- extra_dns_labels
|
- extra_dns_labels
|
||||||
|
- ephemeral
|
||||||
AccessiblePeer:
|
AccessiblePeer:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/PeerMinimum'
|
- $ref: '#/components/schemas/PeerMinimum'
|
||||||
|
|||||||
@@ -1016,6 +1016,9 @@ type Peer struct {
|
|||||||
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
||||||
DnsLabel string `json:"dns_label"`
|
DnsLabel string `json:"dns_label"`
|
||||||
|
|
||||||
|
// Ephemeral Indicates whether the peer is ephemeral or not
|
||||||
|
Ephemeral bool `json:"ephemeral"`
|
||||||
|
|
||||||
// ExtraDnsLabels Extra DNS labels added to the peer
|
// ExtraDnsLabels Extra DNS labels added to the peer
|
||||||
ExtraDnsLabels []string `json:"extra_dns_labels"`
|
ExtraDnsLabels []string `json:"extra_dns_labels"`
|
||||||
|
|
||||||
@@ -1097,6 +1100,9 @@ type PeerBatch struct {
|
|||||||
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
||||||
DnsLabel string `json:"dns_label"`
|
DnsLabel string `json:"dns_label"`
|
||||||
|
|
||||||
|
// Ephemeral Indicates whether the peer is ephemeral or not
|
||||||
|
Ephemeral bool `json:"ephemeral"`
|
||||||
|
|
||||||
// ExtraDnsLabels Extra DNS labels added to the peer
|
// ExtraDnsLabels Extra DNS labels added to the peer
|
||||||
ExtraDnsLabels []string `json:"extra_dns_labels"`
|
ExtraDnsLabels []string `json:"extra_dns_labels"`
|
||||||
|
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD
|
|||||||
CityName: peer.Location.CityName,
|
CityName: peer.Location.CityName,
|
||||||
SerialNumber: peer.Meta.SystemSerialNumber,
|
SerialNumber: peer.Meta.SystemSerialNumber,
|
||||||
InactivityExpirationEnabled: peer.InactivityExpirationEnabled,
|
InactivityExpirationEnabled: peer.InactivityExpirationEnabled,
|
||||||
|
Ephemeral: peer.Ephemeral,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ func (am *DefaultAccountManager) UpdateIntegratedValidatorGroups(ctx context.Con
|
|||||||
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
a, err := am.Store.GetAccountByUser(ctx, userID)
|
return am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||||
|
a, err := transaction.GetAccountByUser(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,8 @@ func (am *DefaultAccountManager) UpdateIntegratedValidatorGroups(ctx context.Con
|
|||||||
a.Settings.Extra = extra
|
a.Settings.Extra = extra
|
||||||
}
|
}
|
||||||
extra.IntegratedValidatorGroups = groups
|
extra.IntegratedValidatorGroups = groups
|
||||||
return am.Store.SaveAccount(ctx, a)
|
return transaction.SaveAccount(ctx, a)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) GroupValidation(ctx context.Context, accountID string, groupIDs []string) (bool, error) {
|
func (am *DefaultAccountManager) GroupValidation(ctx context.Context, accountID string, groupIDs []string) (bool, error) {
|
||||||
|
|||||||
@@ -184,7 +184,9 @@ func (w *Worker) generateProperties(ctx context.Context) properties {
|
|||||||
ephemeralPeersSKs int
|
ephemeralPeersSKs int
|
||||||
ephemeralPeersSKUsage int
|
ephemeralPeersSKUsage int
|
||||||
activePeersLastDay int
|
activePeersLastDay int
|
||||||
|
activeUserPeersLastDay int
|
||||||
osPeers map[string]int
|
osPeers map[string]int
|
||||||
|
activeUsersLastDay map[string]struct{}
|
||||||
userPeers int
|
userPeers int
|
||||||
rules int
|
rules int
|
||||||
rulesProtocol map[string]int
|
rulesProtocol map[string]int
|
||||||
@@ -203,6 +205,7 @@ func (w *Worker) generateProperties(ctx context.Context) properties {
|
|||||||
version string
|
version string
|
||||||
peerActiveVersions []string
|
peerActiveVersions []string
|
||||||
osUIClients map[string]int
|
osUIClients map[string]int
|
||||||
|
rosenpassEnabled int
|
||||||
)
|
)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
metricsProperties := make(properties)
|
metricsProperties := make(properties)
|
||||||
@@ -210,6 +213,7 @@ func (w *Worker) generateProperties(ctx context.Context) properties {
|
|||||||
osUIClients = make(map[string]int)
|
osUIClients = make(map[string]int)
|
||||||
rulesProtocol = make(map[string]int)
|
rulesProtocol = make(map[string]int)
|
||||||
rulesDirection = make(map[string]int)
|
rulesDirection = make(map[string]int)
|
||||||
|
activeUsersLastDay = make(map[string]struct{})
|
||||||
uptime = time.Since(w.startupTime).Seconds()
|
uptime = time.Since(w.startupTime).Seconds()
|
||||||
connections := w.connManager.GetAllConnectedPeers()
|
connections := w.connManager.GetAllConnectedPeers()
|
||||||
version = nbversion.NetbirdVersion()
|
version = nbversion.NetbirdVersion()
|
||||||
@@ -277,10 +281,14 @@ func (w *Worker) generateProperties(ctx context.Context) properties {
|
|||||||
for _, peer := range account.Peers {
|
for _, peer := range account.Peers {
|
||||||
peers++
|
peers++
|
||||||
|
|
||||||
if peer.SSHEnabled {
|
if peer.SSHEnabled || peer.Meta.Flags.ServerSSHAllowed {
|
||||||
peersSSHEnabled++
|
peersSSHEnabled++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if peer.Meta.Flags.RosenpassEnabled {
|
||||||
|
rosenpassEnabled++
|
||||||
|
}
|
||||||
|
|
||||||
if peer.UserID != "" {
|
if peer.UserID != "" {
|
||||||
userPeers++
|
userPeers++
|
||||||
}
|
}
|
||||||
@@ -299,6 +307,10 @@ func (w *Worker) generateProperties(ctx context.Context) properties {
|
|||||||
_, connected := connections[peer.ID]
|
_, connected := connections[peer.ID]
|
||||||
if connected || peer.Status.LastSeen.After(w.lastRun) {
|
if connected || peer.Status.LastSeen.After(w.lastRun) {
|
||||||
activePeersLastDay++
|
activePeersLastDay++
|
||||||
|
if peer.UserID != "" {
|
||||||
|
activeUserPeersLastDay++
|
||||||
|
activeUsersLastDay[peer.UserID] = struct{}{}
|
||||||
|
}
|
||||||
osActiveKey := osKey + "_active"
|
osActiveKey := osKey + "_active"
|
||||||
osActiveCount := osPeers[osActiveKey]
|
osActiveCount := osPeers[osActiveKey]
|
||||||
osPeers[osActiveKey] = osActiveCount + 1
|
osPeers[osActiveKey] = osActiveCount + 1
|
||||||
@@ -320,6 +332,8 @@ func (w *Worker) generateProperties(ctx context.Context) properties {
|
|||||||
metricsProperties["ephemeral_peers_setup_keys"] = ephemeralPeersSKs
|
metricsProperties["ephemeral_peers_setup_keys"] = ephemeralPeersSKs
|
||||||
metricsProperties["ephemeral_peers_setup_keys_usage"] = ephemeralPeersSKUsage
|
metricsProperties["ephemeral_peers_setup_keys_usage"] = ephemeralPeersSKUsage
|
||||||
metricsProperties["active_peers_last_day"] = activePeersLastDay
|
metricsProperties["active_peers_last_day"] = activePeersLastDay
|
||||||
|
metricsProperties["active_user_peers_last_day"] = activeUserPeersLastDay
|
||||||
|
metricsProperties["active_users_last_day"] = len(activeUsersLastDay)
|
||||||
metricsProperties["user_peers"] = userPeers
|
metricsProperties["user_peers"] = userPeers
|
||||||
metricsProperties["rules"] = rules
|
metricsProperties["rules"] = rules
|
||||||
metricsProperties["rules_with_src_posture_checks"] = rulesWithSrcPostureChecks
|
metricsProperties["rules_with_src_posture_checks"] = rulesWithSrcPostureChecks
|
||||||
@@ -338,6 +352,7 @@ func (w *Worker) generateProperties(ctx context.Context) properties {
|
|||||||
metricsProperties["ui_clients"] = uiClient
|
metricsProperties["ui_clients"] = uiClient
|
||||||
metricsProperties["idp_manager"] = w.idpManager
|
metricsProperties["idp_manager"] = w.idpManager
|
||||||
metricsProperties["store_engine"] = w.dataSource.GetStoreEngine()
|
metricsProperties["store_engine"] = w.dataSource.GetStoreEngine()
|
||||||
|
metricsProperties["rosenpass_enabled"] = rosenpassEnabled
|
||||||
|
|
||||||
for protocol, count := range rulesProtocol {
|
for protocol, count := range rulesProtocol {
|
||||||
metricsProperties["rules_protocol_"+protocol] = count
|
metricsProperties["rules_protocol_"+protocol] = count
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ func (mockDatasource) GetAllAccounts(_ context.Context) []*types.Account {
|
|||||||
"1": {
|
"1": {
|
||||||
ID: "1",
|
ID: "1",
|
||||||
UserID: "test",
|
UserID: "test",
|
||||||
SSHEnabled: true,
|
SSHEnabled: false,
|
||||||
Meta: nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
|
Meta: nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1", Flags: nbpeer.Flags{ServerSSHAllowed: true, RosenpassEnabled: true}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Policies: []*types.Policy{
|
Policies: []*types.Policy{
|
||||||
@@ -312,7 +312,19 @@ func TestGenerateProperties(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if properties["posture_checks"] != 2 {
|
if properties["posture_checks"] != 2 {
|
||||||
t.Errorf("expected 1 posture_checks, got %d", properties["posture_checks"])
|
t.Errorf("expected 2 posture_checks, got %d", properties["posture_checks"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["rosenpass_enabled"] != 1 {
|
||||||
|
t.Errorf("expected 1 rosenpass_enabled, got %d", properties["rosenpass_enabled"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["active_user_peers_last_day"] != 2 {
|
||||||
|
t.Errorf("expected 2 active_user_peers_last_day, got %d", properties["active_user_peers_last_day"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["active_users_last_day"] != 1 {
|
||||||
|
t.Errorf("expected 1 active_users_last_day, got %d", properties["active_users_last_day"])
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func (am *DefaultAccountManager) getUserAccessiblePeers(ctx context.Context, acc
|
|||||||
|
|
||||||
// fetch all the peers that have access to the user's peers
|
// fetch all the peers that have access to the user's peers
|
||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
aclPeers, _ := account.GetPeerConnectionResources(ctx, peer.ID, approvedPeersMap)
|
aclPeers, _ := account.GetPeerConnectionResources(ctx, peer, approvedPeersMap)
|
||||||
for _, p := range aclPeers {
|
for _, p := range aclPeers {
|
||||||
peersMap[p.ID] = p
|
peersMap[p.ID] = p
|
||||||
}
|
}
|
||||||
@@ -1149,7 +1149,7 @@ func (am *DefaultAccountManager) checkIfUserOwnsPeer(ctx context.Context, accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range userPeers {
|
for _, p := range userPeers {
|
||||||
aclPeers, _ := account.GetPeerConnectionResources(ctx, p.ID, approvedPeersMap)
|
aclPeers, _ := account.GetPeerConnectionResources(ctx, p, approvedPeersMap)
|
||||||
for _, aclPeer := range aclPeers {
|
for _, aclPeer := range aclPeers {
|
||||||
if aclPeer.ID == peer.ID {
|
if aclPeer.ID == peer.ID {
|
||||||
return peer, nil
|
return peer, nil
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
|||||||
ID: "peerB",
|
ID: "peerB",
|
||||||
IP: net.ParseIP("100.65.80.39"),
|
IP: net.ParseIP("100.65.80.39"),
|
||||||
Status: &nbpeer.PeerStatus{},
|
Status: &nbpeer.PeerStatus{},
|
||||||
|
Meta: nbpeer.PeerSystemMeta{WtVersion: "0.48.0"},
|
||||||
},
|
},
|
||||||
"peerC": {
|
"peerC": {
|
||||||
ID: "peerC",
|
ID: "peerC",
|
||||||
@@ -63,6 +64,12 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
|||||||
IP: net.ParseIP("100.65.31.2"),
|
IP: net.ParseIP("100.65.31.2"),
|
||||||
Status: &nbpeer.PeerStatus{},
|
Status: &nbpeer.PeerStatus{},
|
||||||
},
|
},
|
||||||
|
"peerK": {
|
||||||
|
ID: "peerK",
|
||||||
|
IP: net.ParseIP("100.32.80.1"),
|
||||||
|
Status: &nbpeer.PeerStatus{},
|
||||||
|
Meta: nbpeer.PeerSystemMeta{WtVersion: "0.30.0"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Groups: map[string]*types.Group{
|
Groups: map[string]*types.Group{
|
||||||
"GroupAll": {
|
"GroupAll": {
|
||||||
@@ -111,6 +118,13 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
|||||||
"peerI",
|
"peerI",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"GroupWorkflow": {
|
||||||
|
ID: "GroupWorkflow",
|
||||||
|
Name: "workflow",
|
||||||
|
Peers: []string{
|
||||||
|
"peerK",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Policies: []*types.Policy{
|
Policies: []*types.Policy{
|
||||||
{
|
{
|
||||||
@@ -189,6 +203,39 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "RuleWorkflow",
|
||||||
|
Name: "Workflow",
|
||||||
|
Description: "No description",
|
||||||
|
Enabled: true,
|
||||||
|
Rules: []*types.PolicyRule{
|
||||||
|
{
|
||||||
|
ID: "RuleWorkflow",
|
||||||
|
Name: "Workflow",
|
||||||
|
Description: "No description",
|
||||||
|
Bidirectional: true,
|
||||||
|
Enabled: true,
|
||||||
|
Protocol: types.PolicyRuleProtocolTCP,
|
||||||
|
Action: types.PolicyTrafficActionAccept,
|
||||||
|
PortRanges: []types.RulePortRange{
|
||||||
|
{
|
||||||
|
Start: 8088,
|
||||||
|
End: 8088,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Start: 9090,
|
||||||
|
End: 9095,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Sources: []string{
|
||||||
|
"GroupWorkflow",
|
||||||
|
},
|
||||||
|
Destinations: []string{
|
||||||
|
"GroupDMZ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,14 +246,14 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("check that all peers get map", func(t *testing.T) {
|
t.Run("check that all peers get map", func(t *testing.T) {
|
||||||
for _, p := range account.Peers {
|
for _, p := range account.Peers {
|
||||||
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), p.ID, validatedPeers)
|
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), p, validatedPeers)
|
||||||
assert.GreaterOrEqual(t, len(peers), 2, "minimum number peers should present")
|
assert.GreaterOrEqual(t, len(peers), 1, "minimum number peers should present")
|
||||||
assert.GreaterOrEqual(t, len(firewallRules), 2, "minimum number of firewall rules should present")
|
assert.GreaterOrEqual(t, len(firewallRules), 1, "minimum number of firewall rules should present")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("check first peer map details", func(t *testing.T) {
|
t.Run("check first peer map details", func(t *testing.T) {
|
||||||
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), "peerB", validatedPeers)
|
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], validatedPeers)
|
||||||
assert.Len(t, peers, 8)
|
assert.Len(t, peers, 8)
|
||||||
assert.Contains(t, peers, account.Peers["peerA"])
|
assert.Contains(t, peers, account.Peers["peerA"])
|
||||||
assert.Contains(t, peers, account.Peers["peerC"])
|
assert.Contains(t, peers, account.Peers["peerC"])
|
||||||
@@ -364,6 +411,32 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
|
|||||||
assert.True(t, contains, "rule not found in expected rules %#v", rule)
|
assert.True(t, contains, "rule not found in expected rules %#v", rule)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("check port ranges support for older peers", func(t *testing.T) {
|
||||||
|
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), account.Peers["peerK"], validatedPeers)
|
||||||
|
assert.Len(t, peers, 1)
|
||||||
|
assert.Contains(t, peers, account.Peers["peerI"])
|
||||||
|
|
||||||
|
expectedFirewallRules := []*types.FirewallRule{
|
||||||
|
{
|
||||||
|
PeerIP: "100.65.31.2",
|
||||||
|
Direction: types.FirewallRuleDirectionIN,
|
||||||
|
Action: "accept",
|
||||||
|
Protocol: "tcp",
|
||||||
|
Port: "8088",
|
||||||
|
PolicyID: "RuleWorkflow",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PeerIP: "100.65.31.2",
|
||||||
|
Direction: types.FirewallRuleDirectionOUT,
|
||||||
|
Action: "accept",
|
||||||
|
Protocol: "tcp",
|
||||||
|
Port: "8088",
|
||||||
|
PolicyID: "RuleWorkflow",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(t, firewallRules, expectedFirewallRules)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccount_getPeersByPolicyDirect(t *testing.T) {
|
func TestAccount_getPeersByPolicyDirect(t *testing.T) {
|
||||||
@@ -466,10 +539,10 @@ func TestAccount_getPeersByPolicyDirect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("check first peer map", func(t *testing.T) {
|
t.Run("check first peer map", func(t *testing.T) {
|
||||||
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), "peerB", approvedPeers)
|
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], approvedPeers)
|
||||||
assert.Contains(t, peers, account.Peers["peerC"])
|
assert.Contains(t, peers, account.Peers["peerC"])
|
||||||
|
|
||||||
epectedFirewallRules := []*types.FirewallRule{
|
expectedFirewallRules := []*types.FirewallRule{
|
||||||
{
|
{
|
||||||
PeerIP: "100.65.254.139",
|
PeerIP: "100.65.254.139",
|
||||||
Direction: types.FirewallRuleDirectionIN,
|
Direction: types.FirewallRuleDirectionIN,
|
||||||
@@ -487,19 +560,19 @@ func TestAccount_getPeersByPolicyDirect(t *testing.T) {
|
|||||||
PolicyID: "RuleSwarm",
|
PolicyID: "RuleSwarm",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Len(t, firewallRules, len(epectedFirewallRules))
|
assert.Len(t, firewallRules, len(expectedFirewallRules))
|
||||||
slices.SortFunc(epectedFirewallRules, sortFunc())
|
slices.SortFunc(expectedFirewallRules, sortFunc())
|
||||||
slices.SortFunc(firewallRules, sortFunc())
|
slices.SortFunc(firewallRules, sortFunc())
|
||||||
for i := range firewallRules {
|
for i := range firewallRules {
|
||||||
assert.Equal(t, epectedFirewallRules[i], firewallRules[i])
|
assert.Equal(t, expectedFirewallRules[i], firewallRules[i])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("check second peer map", func(t *testing.T) {
|
t.Run("check second peer map", func(t *testing.T) {
|
||||||
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), "peerC", approvedPeers)
|
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), account.Peers["peerC"], approvedPeers)
|
||||||
assert.Contains(t, peers, account.Peers["peerB"])
|
assert.Contains(t, peers, account.Peers["peerB"])
|
||||||
|
|
||||||
epectedFirewallRules := []*types.FirewallRule{
|
expectedFirewallRules := []*types.FirewallRule{
|
||||||
{
|
{
|
||||||
PeerIP: "100.65.80.39",
|
PeerIP: "100.65.80.39",
|
||||||
Direction: types.FirewallRuleDirectionIN,
|
Direction: types.FirewallRuleDirectionIN,
|
||||||
@@ -517,21 +590,21 @@ func TestAccount_getPeersByPolicyDirect(t *testing.T) {
|
|||||||
PolicyID: "RuleSwarm",
|
PolicyID: "RuleSwarm",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Len(t, firewallRules, len(epectedFirewallRules))
|
assert.Len(t, firewallRules, len(expectedFirewallRules))
|
||||||
slices.SortFunc(epectedFirewallRules, sortFunc())
|
slices.SortFunc(expectedFirewallRules, sortFunc())
|
||||||
slices.SortFunc(firewallRules, sortFunc())
|
slices.SortFunc(firewallRules, sortFunc())
|
||||||
for i := range firewallRules {
|
for i := range firewallRules {
|
||||||
assert.Equal(t, epectedFirewallRules[i], firewallRules[i])
|
assert.Equal(t, expectedFirewallRules[i], firewallRules[i])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
account.Policies[1].Rules[0].Bidirectional = false
|
account.Policies[1].Rules[0].Bidirectional = false
|
||||||
|
|
||||||
t.Run("check first peer map directional only", func(t *testing.T) {
|
t.Run("check first peer map directional only", func(t *testing.T) {
|
||||||
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), "peerB", approvedPeers)
|
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], approvedPeers)
|
||||||
assert.Contains(t, peers, account.Peers["peerC"])
|
assert.Contains(t, peers, account.Peers["peerC"])
|
||||||
|
|
||||||
epectedFirewallRules := []*types.FirewallRule{
|
expectedFirewallRules := []*types.FirewallRule{
|
||||||
{
|
{
|
||||||
PeerIP: "100.65.254.139",
|
PeerIP: "100.65.254.139",
|
||||||
Direction: types.FirewallRuleDirectionOUT,
|
Direction: types.FirewallRuleDirectionOUT,
|
||||||
@@ -541,19 +614,19 @@ func TestAccount_getPeersByPolicyDirect(t *testing.T) {
|
|||||||
PolicyID: "RuleSwarm",
|
PolicyID: "RuleSwarm",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Len(t, firewallRules, len(epectedFirewallRules))
|
assert.Len(t, firewallRules, len(expectedFirewallRules))
|
||||||
slices.SortFunc(epectedFirewallRules, sortFunc())
|
slices.SortFunc(expectedFirewallRules, sortFunc())
|
||||||
slices.SortFunc(firewallRules, sortFunc())
|
slices.SortFunc(firewallRules, sortFunc())
|
||||||
for i := range firewallRules {
|
for i := range firewallRules {
|
||||||
assert.Equal(t, epectedFirewallRules[i], firewallRules[i])
|
assert.Equal(t, expectedFirewallRules[i], firewallRules[i])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("check second peer map directional only", func(t *testing.T) {
|
t.Run("check second peer map directional only", func(t *testing.T) {
|
||||||
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), "peerC", approvedPeers)
|
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), account.Peers["peerC"], approvedPeers)
|
||||||
assert.Contains(t, peers, account.Peers["peerB"])
|
assert.Contains(t, peers, account.Peers["peerB"])
|
||||||
|
|
||||||
epectedFirewallRules := []*types.FirewallRule{
|
expectedFirewallRules := []*types.FirewallRule{
|
||||||
{
|
{
|
||||||
PeerIP: "100.65.80.39",
|
PeerIP: "100.65.80.39",
|
||||||
Direction: types.FirewallRuleDirectionIN,
|
Direction: types.FirewallRuleDirectionIN,
|
||||||
@@ -563,11 +636,11 @@ func TestAccount_getPeersByPolicyDirect(t *testing.T) {
|
|||||||
PolicyID: "RuleSwarm",
|
PolicyID: "RuleSwarm",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Len(t, firewallRules, len(epectedFirewallRules))
|
assert.Len(t, firewallRules, len(expectedFirewallRules))
|
||||||
slices.SortFunc(epectedFirewallRules, sortFunc())
|
slices.SortFunc(expectedFirewallRules, sortFunc())
|
||||||
slices.SortFunc(firewallRules, sortFunc())
|
slices.SortFunc(firewallRules, sortFunc())
|
||||||
for i := range firewallRules {
|
for i := range firewallRules {
|
||||||
assert.Equal(t, epectedFirewallRules[i], firewallRules[i])
|
assert.Equal(t, expectedFirewallRules[i], firewallRules[i])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -748,7 +821,7 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
|||||||
t.Run("verify peer's network map with default group peer list", func(t *testing.T) {
|
t.Run("verify peer's network map with default group peer list", func(t *testing.T) {
|
||||||
// peerB doesn't fulfill the NB posture check but is included in the destination group Swarm,
|
// peerB doesn't fulfill the NB posture check but is included in the destination group Swarm,
|
||||||
// will establish a connection with all source peers satisfying the NB posture check.
|
// will establish a connection with all source peers satisfying the NB posture check.
|
||||||
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), "peerB", approvedPeers)
|
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], approvedPeers)
|
||||||
assert.Len(t, peers, 4)
|
assert.Len(t, peers, 4)
|
||||||
assert.Len(t, firewallRules, 4)
|
assert.Len(t, firewallRules, 4)
|
||||||
assert.Contains(t, peers, account.Peers["peerA"])
|
assert.Contains(t, peers, account.Peers["peerA"])
|
||||||
@@ -758,7 +831,7 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
|||||||
|
|
||||||
// peerC satisfy the NB posture check, should establish connection to all destination group peer's
|
// peerC satisfy the NB posture check, should establish connection to all destination group peer's
|
||||||
// We expect a single permissive firewall rule which all outgoing connections
|
// We expect a single permissive firewall rule which all outgoing connections
|
||||||
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), "peerC", approvedPeers)
|
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), account.Peers["peerC"], approvedPeers)
|
||||||
assert.Len(t, peers, len(account.Groups["GroupSwarm"].Peers))
|
assert.Len(t, peers, len(account.Groups["GroupSwarm"].Peers))
|
||||||
assert.Len(t, firewallRules, 1)
|
assert.Len(t, firewallRules, 1)
|
||||||
expectedFirewallRules := []*types.FirewallRule{
|
expectedFirewallRules := []*types.FirewallRule{
|
||||||
@@ -775,7 +848,7 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
|||||||
|
|
||||||
// peerE doesn't fulfill the NB posture check and exists in only destination group Swarm,
|
// peerE doesn't fulfill the NB posture check and exists in only destination group Swarm,
|
||||||
// all source group peers satisfying the NB posture check should establish connection
|
// all source group peers satisfying the NB posture check should establish connection
|
||||||
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), "peerE", approvedPeers)
|
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), account.Peers["peerE"], approvedPeers)
|
||||||
assert.Len(t, peers, 4)
|
assert.Len(t, peers, 4)
|
||||||
assert.Len(t, firewallRules, 4)
|
assert.Len(t, firewallRules, 4)
|
||||||
assert.Contains(t, peers, account.Peers["peerA"])
|
assert.Contains(t, peers, account.Peers["peerA"])
|
||||||
@@ -785,7 +858,7 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
|||||||
|
|
||||||
// peerI doesn't fulfill the OS version posture check and exists in only destination group Swarm,
|
// peerI doesn't fulfill the OS version posture check and exists in only destination group Swarm,
|
||||||
// all source group peers satisfying the NB posture check should establish connection
|
// all source group peers satisfying the NB posture check should establish connection
|
||||||
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), "peerI", approvedPeers)
|
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), account.Peers["peerI"], approvedPeers)
|
||||||
assert.Len(t, peers, 4)
|
assert.Len(t, peers, 4)
|
||||||
assert.Len(t, firewallRules, 4)
|
assert.Len(t, firewallRules, 4)
|
||||||
assert.Contains(t, peers, account.Peers["peerA"])
|
assert.Contains(t, peers, account.Peers["peerA"])
|
||||||
@@ -800,19 +873,19 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
|||||||
|
|
||||||
// peerB doesn't satisfy the NB posture check, and doesn't exist in destination group peer's
|
// peerB doesn't satisfy the NB posture check, and doesn't exist in destination group peer's
|
||||||
// no connection should be established to any peer of destination group
|
// no connection should be established to any peer of destination group
|
||||||
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), "peerB", approvedPeers)
|
peers, firewallRules := account.GetPeerConnectionResources(context.Background(), account.Peers["peerB"], approvedPeers)
|
||||||
assert.Len(t, peers, 0)
|
assert.Len(t, peers, 0)
|
||||||
assert.Len(t, firewallRules, 0)
|
assert.Len(t, firewallRules, 0)
|
||||||
|
|
||||||
// peerI doesn't satisfy the OS version posture check, and doesn't exist in destination group peer's
|
// peerI doesn't satisfy the OS version posture check, and doesn't exist in destination group peer's
|
||||||
// no connection should be established to any peer of destination group
|
// no connection should be established to any peer of destination group
|
||||||
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), "peerI", approvedPeers)
|
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), account.Peers["peerI"], approvedPeers)
|
||||||
assert.Len(t, peers, 0)
|
assert.Len(t, peers, 0)
|
||||||
assert.Len(t, firewallRules, 0)
|
assert.Len(t, firewallRules, 0)
|
||||||
|
|
||||||
// peerC satisfy the NB posture check, should establish connection to all destination group peer's
|
// peerC satisfy the NB posture check, should establish connection to all destination group peer's
|
||||||
// We expect a single permissive firewall rule which all outgoing connections
|
// We expect a single permissive firewall rule which all outgoing connections
|
||||||
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), "peerC", approvedPeers)
|
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), account.Peers["peerC"], approvedPeers)
|
||||||
assert.Len(t, peers, len(account.Groups["GroupSwarm"].Peers))
|
assert.Len(t, peers, len(account.Groups["GroupSwarm"].Peers))
|
||||||
assert.Len(t, firewallRules, len(account.Groups["GroupSwarm"].Peers))
|
assert.Len(t, firewallRules, len(account.Groups["GroupSwarm"].Peers))
|
||||||
|
|
||||||
@@ -827,14 +900,14 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) {
|
|||||||
|
|
||||||
// peerE doesn't fulfill the NB posture check and exists in only destination group Swarm,
|
// peerE doesn't fulfill the NB posture check and exists in only destination group Swarm,
|
||||||
// all source group peers satisfying the NB posture check should establish connection
|
// all source group peers satisfying the NB posture check should establish connection
|
||||||
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), "peerE", approvedPeers)
|
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), account.Peers["peerE"], approvedPeers)
|
||||||
assert.Len(t, peers, 3)
|
assert.Len(t, peers, 3)
|
||||||
assert.Len(t, firewallRules, 3)
|
assert.Len(t, firewallRules, 3)
|
||||||
assert.Contains(t, peers, account.Peers["peerA"])
|
assert.Contains(t, peers, account.Peers["peerA"])
|
||||||
assert.Contains(t, peers, account.Peers["peerC"])
|
assert.Contains(t, peers, account.Peers["peerC"])
|
||||||
assert.Contains(t, peers, account.Peers["peerD"])
|
assert.Contains(t, peers, account.Peers["peerD"])
|
||||||
|
|
||||||
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), "peerA", approvedPeers)
|
peers, firewallRules = account.GetPeerConnectionResources(context.Background(), account.Peers["peerA"], approvedPeers)
|
||||||
assert.Len(t, peers, 5)
|
assert.Len(t, peers, 5)
|
||||||
// assert peers from Group Swarm
|
// assert peers from Group Swarm
|
||||||
assert.Contains(t, peers, account.Peers["peerD"])
|
assert.Contains(t, peers, account.Peers["peerD"])
|
||||||
|
|||||||
@@ -24,20 +24,12 @@ func sanitizeVersion(version string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *NBVersionCheck) Check(ctx context.Context, peer nbpeer.Peer) (bool, error) {
|
func (n *NBVersionCheck) Check(ctx context.Context, peer nbpeer.Peer) (bool, error) {
|
||||||
peerVersion := sanitizeVersion(peer.Meta.WtVersion)
|
meetsMin, err := MeetsMinVersion(n.MinVersion, peer.Meta.WtVersion)
|
||||||
minVersion := sanitizeVersion(n.MinVersion)
|
|
||||||
|
|
||||||
peerNBVersion, err := version.NewVersion(peerVersion)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
constraints, err := version.NewConstraint(">= " + minVersion)
|
if meetsMin {
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if constraints.Check(peerNBVersion) {
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,3 +52,21 @@ func (n *NBVersionCheck) Validate() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MeetsMinVersion checks if the peer's version meets or exceeds the minimum required version
|
||||||
|
func MeetsMinVersion(minVer, peerVer string) (bool, error) {
|
||||||
|
peerVer = sanitizeVersion(peerVer)
|
||||||
|
minVer = sanitizeVersion(minVer)
|
||||||
|
|
||||||
|
peerNBVer, err := version.NewVersion(peerVer)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
constraints, err := version.NewConstraint(">= " + minVer)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return constraints.Check(peerNBVer), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -139,3 +139,68 @@ func TestNBVersionCheck_Validate(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMeetsMinVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
minVer string
|
||||||
|
peerVer string
|
||||||
|
want bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Peer version greater than min version",
|
||||||
|
minVer: "0.26.0",
|
||||||
|
peerVer: "0.60.1",
|
||||||
|
want: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer version equals min version",
|
||||||
|
minVer: "1.0.0",
|
||||||
|
peerVer: "1.0.0",
|
||||||
|
want: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer version less than min version",
|
||||||
|
minVer: "1.0.0",
|
||||||
|
peerVer: "0.9.9",
|
||||||
|
want: false,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer version with pre-release tag greater than min version",
|
||||||
|
minVer: "1.0.0",
|
||||||
|
peerVer: "1.0.1-alpha",
|
||||||
|
want: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid peer version format",
|
||||||
|
minVer: "1.0.0",
|
||||||
|
peerVer: "dev",
|
||||||
|
want: false,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid min version format",
|
||||||
|
minVer: "invalid.version",
|
||||||
|
peerVer: "1.0.0",
|
||||||
|
want: false,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := MeetsMinVersion(tt.minVer, tt.peerVer)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"slices"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
|
||||||
"github.com/netbirdio/netbird/management/server/permissions/operations"
|
|
||||||
"github.com/netbirdio/netbird/management/server/store"
|
|
||||||
"github.com/netbirdio/netbird/management/server/types"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/domain"
|
"github.com/netbirdio/netbird/management/domain"
|
||||||
"github.com/netbirdio/netbird/management/proto"
|
"github.com/netbirdio/netbird/management/proto"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
||||||
|
"github.com/netbirdio/netbird/management/server/permissions/operations"
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
"github.com/netbirdio/netbird/management/server/store"
|
||||||
|
"github.com/netbirdio/netbird/management/server/types"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,13 +30,19 @@ func (am *DefaultAccountManager) GetRoute(ctx context.Context, accountID string,
|
|||||||
return nil, status.NewPermissionDeniedError()
|
return nil, status.NewPermissionDeniedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return am.Store.GetRouteByID(ctx, store.LockingStrengthShare, string(routeID), accountID)
|
return am.Store.GetRouteByID(ctx, store.LockingStrengthShare, accountID, string(routeID))
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkRoutePrefixOrDomainsExistForPeers checks if a route with a given prefix exists for a single peer or multiple peer groups.
|
// checkRoutePrefixOrDomainsExistForPeers checks if a route with a given prefix exists for a single peer or multiple peer groups.
|
||||||
func (am *DefaultAccountManager) checkRoutePrefixOrDomainsExistForPeers(account *types.Account, peerID string, routeID route.ID, peerGroupIDs []string, prefix netip.Prefix, domains domain.List) error {
|
func checkRoutePrefixOrDomainsExistForPeers(ctx context.Context, transaction store.Store, accountID string, checkRoute *route.Route, groupsMap map[string]*types.Group) error {
|
||||||
// routes can have both peer and peer_groups
|
// routes can have both peer and peer_groups
|
||||||
routesWithPrefix := account.GetRoutesByPrefixOrDomains(prefix, domains)
|
prefix := checkRoute.Network
|
||||||
|
domains := checkRoute.Domains
|
||||||
|
|
||||||
|
routesWithPrefix, err := getRoutesByPrefixOrDomains(ctx, transaction, accountID, prefix, domains)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// lets remember all the peers and the peer groups from routesWithPrefix
|
// lets remember all the peers and the peer groups from routesWithPrefix
|
||||||
seenPeers := make(map[string]bool)
|
seenPeers := make(map[string]bool)
|
||||||
@@ -45,18 +51,24 @@ func (am *DefaultAccountManager) checkRoutePrefixOrDomainsExistForPeers(account
|
|||||||
for _, prefixRoute := range routesWithPrefix {
|
for _, prefixRoute := range routesWithPrefix {
|
||||||
// we skip route(s) with the same network ID as we want to allow updating of the existing route
|
// we skip route(s) with the same network ID as we want to allow updating of the existing route
|
||||||
// when creating a new route routeID is newly generated so nothing will be skipped
|
// when creating a new route routeID is newly generated so nothing will be skipped
|
||||||
if routeID == prefixRoute.ID {
|
if checkRoute.ID == prefixRoute.ID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if prefixRoute.Peer != "" {
|
if prefixRoute.Peer != "" {
|
||||||
seenPeers[string(prefixRoute.ID)] = true
|
seenPeers[string(prefixRoute.ID)] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
peerGroupsMap, err := transaction.GetGroupsByIDs(ctx, store.LockingStrengthShare, accountID, prefixRoute.PeerGroups)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
for _, groupID := range prefixRoute.PeerGroups {
|
for _, groupID := range prefixRoute.PeerGroups {
|
||||||
seenPeerGroups[groupID] = true
|
seenPeerGroups[groupID] = true
|
||||||
|
|
||||||
group := account.GetGroup(groupID)
|
group, ok := peerGroupsMap[groupID]
|
||||||
if group == nil {
|
if !ok || group == nil {
|
||||||
return status.Errorf(
|
return status.Errorf(
|
||||||
status.InvalidArgument, "failed to add route with %s - peer group %s doesn't exist",
|
status.InvalidArgument, "failed to add route with %s - peer group %s doesn't exist",
|
||||||
getRouteDescriptor(prefix, domains), groupID,
|
getRouteDescriptor(prefix, domains), groupID,
|
||||||
@@ -69,12 +81,13 @@ func (am *DefaultAccountManager) checkRoutePrefixOrDomainsExistForPeers(account
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if peerID != "" {
|
if peerID := checkRoute.Peer; peerID != "" {
|
||||||
// check that peerID exists and is not in any route as single peer or part of the group
|
// check that peerID exists and is not in any route as single peer or part of the group
|
||||||
peer := account.GetPeer(peerID)
|
_, err = transaction.GetPeerByID(context.Background(), store.LockingStrengthShare, accountID, peerID)
|
||||||
if peer == nil {
|
if err != nil {
|
||||||
return status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
return status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := seenPeers[peerID]; ok {
|
if _, ok := seenPeers[peerID]; ok {
|
||||||
return status.Errorf(status.AlreadyExists,
|
return status.Errorf(status.AlreadyExists,
|
||||||
"failed to add route with %s - peer %s already has this route", getRouteDescriptor(prefix, domains), peerID)
|
"failed to add route with %s - peer %s already has this route", getRouteDescriptor(prefix, domains), peerID)
|
||||||
@@ -82,9 +95,8 @@ func (am *DefaultAccountManager) checkRoutePrefixOrDomainsExistForPeers(account
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check that peerGroupIDs are not in any route peerGroups list
|
// check that peerGroupIDs are not in any route peerGroups list
|
||||||
for _, groupID := range peerGroupIDs {
|
for _, groupID := range checkRoute.PeerGroups {
|
||||||
group := account.GetGroup(groupID) // we validated the group existence before entering this function, no need to check again.
|
group := groupsMap[groupID] // we validated the group existence before entering this function, no need to check again.
|
||||||
|
|
||||||
if _, ok := seenPeerGroups[groupID]; ok {
|
if _, ok := seenPeerGroups[groupID]; ok {
|
||||||
return status.Errorf(
|
return status.Errorf(
|
||||||
status.AlreadyExists, "failed to add route with %s - peer group %s already has this route",
|
status.AlreadyExists, "failed to add route with %s - peer group %s already has this route",
|
||||||
@@ -92,12 +104,18 @@ func (am *DefaultAccountManager) checkRoutePrefixOrDomainsExistForPeers(account
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check that the peers from peerGroupIDs groups are not the same peers we saw in routesWithPrefix
|
// check that the peers from peerGroupIDs groups are not the same peers we saw in routesWithPrefix
|
||||||
|
peersMap, err := transaction.GetPeersByIDs(ctx, store.LockingStrengthShare, accountID, group.Peers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
for _, id := range group.Peers {
|
for _, id := range group.Peers {
|
||||||
if _, ok := seenPeers[id]; ok {
|
if _, ok := seenPeers[id]; ok {
|
||||||
peer := account.GetPeer(id)
|
peer, ok := peersMap[id]
|
||||||
if peer == nil {
|
if !ok || peer == nil {
|
||||||
return status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
return status.Errorf(status.InvalidArgument, "peer with ID %s not found", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return status.Errorf(status.AlreadyExists,
|
return status.Errorf(status.AlreadyExists,
|
||||||
"failed to add route with %s - peer %s from the group %s already has this route",
|
"failed to add route with %s - peer %s from the group %s already has this route",
|
||||||
getRouteDescriptor(prefix, domains), peer.Name, group.Name)
|
getRouteDescriptor(prefix, domains), peer.Name, group.Name)
|
||||||
@@ -128,97 +146,58 @@ func (am *DefaultAccountManager) CreateRoute(ctx context.Context, accountID stri
|
|||||||
return nil, status.NewPermissionDeniedError()
|
return nil, status.NewPermissionDeniedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(ctx, accountID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(domains) > 0 && prefix.IsValid() {
|
if len(domains) > 0 && prefix.IsValid() {
|
||||||
return nil, status.Errorf(status.InvalidArgument, "domains and network should not be provided at the same time")
|
return nil, status.Errorf(status.InvalidArgument, "domains and network should not be provided at the same time")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(domains) == 0 && !prefix.IsValid() {
|
var newRoute *route.Route
|
||||||
return nil, status.Errorf(status.InvalidArgument, "invalid Prefix")
|
var updateAccountPeers bool
|
||||||
|
|
||||||
|
err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||||
|
newRoute = &route.Route{
|
||||||
|
ID: route.ID(xid.New().String()),
|
||||||
|
AccountID: accountID,
|
||||||
|
Network: prefix,
|
||||||
|
Domains: domains,
|
||||||
|
KeepRoute: keepRoute,
|
||||||
|
NetID: netID,
|
||||||
|
Description: description,
|
||||||
|
Peer: peerID,
|
||||||
|
PeerGroups: peerGroupIDs,
|
||||||
|
NetworkType: networkType,
|
||||||
|
Masquerade: masquerade,
|
||||||
|
Metric: metric,
|
||||||
|
Enabled: enabled,
|
||||||
|
Groups: groups,
|
||||||
|
AccessControlGroups: accessControlGroupIDs,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(domains) > 0 {
|
if err = validateRoute(ctx, transaction, accountID, newRoute); err != nil {
|
||||||
prefix = getPlaceholderIP()
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if peerID != "" && len(peerGroupIDs) != 0 {
|
updateAccountPeers, err = areRouteChangesAffectPeers(ctx, transaction, newRoute)
|
||||||
return nil, status.Errorf(
|
if err != nil {
|
||||||
status.InvalidArgument,
|
return err
|
||||||
"peer with ID %s and peers group %s should not be provided at the same time",
|
|
||||||
peerID, peerGroupIDs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var newRoute route.Route
|
if err = transaction.IncrementNetworkSerial(ctx, store.LockingStrengthUpdate, accountID); err != nil {
|
||||||
newRoute.ID = route.ID(xid.New().String())
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(peerGroupIDs) > 0 {
|
return transaction.SaveRoute(ctx, store.LockingStrengthUpdate, newRoute)
|
||||||
err = validateGroups(peerGroupIDs, account.Groups)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if len(accessControlGroupIDs) > 0 {
|
|
||||||
err = validateGroups(accessControlGroupIDs, account.Groups)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = am.checkRoutePrefixOrDomainsExistForPeers(account, peerID, newRoute.ID, peerGroupIDs, prefix, domains)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if metric < route.MinMetric || metric > route.MaxMetric {
|
|
||||||
return nil, status.Errorf(status.InvalidArgument, "metric should be between %d and %d", route.MinMetric, route.MaxMetric)
|
|
||||||
}
|
|
||||||
|
|
||||||
if utf8.RuneCountInString(string(netID)) > route.MaxNetIDChar || netID == "" {
|
|
||||||
return nil, status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d", route.MaxNetIDChar)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = validateGroups(groups, account.Groups)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newRoute.Peer = peerID
|
|
||||||
newRoute.PeerGroups = peerGroupIDs
|
|
||||||
newRoute.Network = prefix
|
|
||||||
newRoute.Domains = domains
|
|
||||||
newRoute.NetworkType = networkType
|
|
||||||
newRoute.Description = description
|
|
||||||
newRoute.NetID = netID
|
|
||||||
newRoute.Masquerade = masquerade
|
|
||||||
newRoute.Metric = metric
|
|
||||||
newRoute.Enabled = enabled
|
|
||||||
newRoute.Groups = groups
|
|
||||||
newRoute.KeepRoute = keepRoute
|
|
||||||
newRoute.AccessControlGroups = accessControlGroupIDs
|
|
||||||
|
|
||||||
if account.Routes == nil {
|
|
||||||
account.Routes = make(map[route.ID]*route.Route)
|
|
||||||
}
|
|
||||||
|
|
||||||
account.Routes[newRoute.ID] = &newRoute
|
|
||||||
|
|
||||||
account.Network.IncSerial()
|
|
||||||
if err = am.Store.SaveAccount(ctx, account); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if am.isRouteChangeAffectPeers(account, &newRoute) {
|
|
||||||
am.UpdateAccountPeers(ctx, accountID)
|
|
||||||
}
|
|
||||||
|
|
||||||
am.StoreEvent(ctx, userID, string(newRoute.ID), accountID, activity.RouteCreated, newRoute.EventMeta())
|
am.StoreEvent(ctx, userID, string(newRoute.ID), accountID, activity.RouteCreated, newRoute.EventMeta())
|
||||||
|
|
||||||
return &newRoute, nil
|
if updateAccountPeers {
|
||||||
|
am.UpdateAccountPeers(ctx, accountID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRoute, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveRoute saves route
|
// SaveRoute saves route
|
||||||
@@ -226,6 +205,115 @@ func (am *DefaultAccountManager) SaveRoute(ctx context.Context, accountID, userI
|
|||||||
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
|
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Routes, operations.Update)
|
||||||
|
if err != nil {
|
||||||
|
return status.NewPermissionValidationError(err)
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
return status.NewPermissionDeniedError()
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldRoute *route.Route
|
||||||
|
var oldRouteAffectsPeers bool
|
||||||
|
var newRouteAffectsPeers bool
|
||||||
|
|
||||||
|
err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||||
|
if err = validateRoute(ctx, transaction, accountID, routeToSave); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldRoute, err = transaction.GetRouteByID(ctx, store.LockingStrengthUpdate, accountID, string(routeToSave.ID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldRouteAffectsPeers, err = areRouteChangesAffectPeers(ctx, transaction, oldRoute)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newRouteAffectsPeers, err = areRouteChangesAffectPeers(ctx, transaction, routeToSave)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
routeToSave.AccountID = accountID
|
||||||
|
|
||||||
|
if err = transaction.IncrementNetworkSerial(ctx, store.LockingStrengthUpdate, accountID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return transaction.SaveRoute(ctx, store.LockingStrengthUpdate, routeToSave)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
am.StoreEvent(ctx, userID, string(routeToSave.ID), accountID, activity.RouteUpdated, routeToSave.EventMeta())
|
||||||
|
|
||||||
|
if oldRouteAffectsPeers || newRouteAffectsPeers {
|
||||||
|
am.UpdateAccountPeers(ctx, accountID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRoute deletes route with routeID
|
||||||
|
func (am *DefaultAccountManager) DeleteRoute(ctx context.Context, accountID string, routeID route.ID, userID string) error {
|
||||||
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Routes, operations.Delete)
|
||||||
|
if err != nil {
|
||||||
|
return status.NewPermissionValidationError(err)
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
return status.NewPermissionDeniedError()
|
||||||
|
}
|
||||||
|
|
||||||
|
var route *route.Route
|
||||||
|
var updateAccountPeers bool
|
||||||
|
|
||||||
|
err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||||
|
route, err = transaction.GetRouteByID(ctx, store.LockingStrengthUpdate, accountID, string(routeID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAccountPeers, err = areRouteChangesAffectPeers(ctx, transaction, route)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = transaction.IncrementNetworkSerial(ctx, store.LockingStrengthUpdate, accountID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return transaction.DeleteRoute(ctx, store.LockingStrengthUpdate, accountID, string(routeID))
|
||||||
|
})
|
||||||
|
|
||||||
|
am.StoreEvent(ctx, userID, string(route.ID), accountID, activity.RouteRemoved, route.EventMeta())
|
||||||
|
|
||||||
|
if updateAccountPeers {
|
||||||
|
am.UpdateAccountPeers(ctx, accountID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRoutes returns a list of routes from account
|
||||||
|
func (am *DefaultAccountManager) ListRoutes(ctx context.Context, accountID, userID string) ([]*route.Route, error) {
|
||||||
|
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Routes, operations.Read)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.NewPermissionValidationError(err)
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
return nil, status.NewPermissionDeniedError()
|
||||||
|
}
|
||||||
|
|
||||||
|
return am.Store.GetAccountRoutes(ctx, store.LockingStrengthShare, accountID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRoute(ctx context.Context, transaction store.Store, accountID string, routeToSave *route.Route) error {
|
||||||
if routeToSave == nil {
|
if routeToSave == nil {
|
||||||
return status.Errorf(status.InvalidArgument, "route provided is nil")
|
return status.Errorf(status.InvalidArgument, "route provided is nil")
|
||||||
}
|
}
|
||||||
@@ -238,19 +326,6 @@ func (am *DefaultAccountManager) SaveRoute(ctx context.Context, accountID, userI
|
|||||||
return status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d", route.MaxNetIDChar)
|
return status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d", route.MaxNetIDChar)
|
||||||
}
|
}
|
||||||
|
|
||||||
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Routes, operations.Update)
|
|
||||||
if err != nil {
|
|
||||||
return status.NewPermissionValidationError(err)
|
|
||||||
}
|
|
||||||
if !allowed {
|
|
||||||
return status.NewPermissionDeniedError()
|
|
||||||
}
|
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(ctx, accountID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(routeToSave.Domains) > 0 && routeToSave.Network.IsValid() {
|
if len(routeToSave.Domains) > 0 && routeToSave.Network.IsValid() {
|
||||||
return status.Errorf(status.InvalidArgument, "domains and network should not be provided at the same time")
|
return status.Errorf(status.InvalidArgument, "domains and network should not be provided at the same time")
|
||||||
}
|
}
|
||||||
@@ -267,96 +342,39 @@ func (am *DefaultAccountManager) SaveRoute(ctx context.Context, accountID, userI
|
|||||||
return status.Errorf(status.InvalidArgument, "peer with ID and peer groups should not be provided at the same time")
|
return status.Errorf(status.InvalidArgument, "peer with ID and peer groups should not be provided at the same time")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(routeToSave.PeerGroups) > 0 {
|
groupsMap, err := validateRouteGroups(ctx, transaction, accountID, routeToSave)
|
||||||
err = validateGroups(routeToSave.PeerGroups, account.Groups)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return checkRoutePrefixOrDomainsExistForPeers(ctx, transaction, accountID, routeToSave, groupsMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateRouteGroups validates the route groups and returns the validated groups map.
|
||||||
|
func validateRouteGroups(ctx context.Context, transaction store.Store, accountID string, routeToSave *route.Route) (map[string]*types.Group, error) {
|
||||||
|
groupsToValidate := slices.Concat(routeToSave.Groups, routeToSave.PeerGroups, routeToSave.AccessControlGroups)
|
||||||
|
groupsMap, err := transaction.GetGroupsByIDs(ctx, store.LockingStrengthShare, accountID, groupsToValidate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(routeToSave.PeerGroups) > 0 {
|
||||||
|
if err = validateGroups(routeToSave.PeerGroups, groupsMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(routeToSave.AccessControlGroups) > 0 {
|
if len(routeToSave.AccessControlGroups) > 0 {
|
||||||
err = validateGroups(routeToSave.AccessControlGroups, account.Groups)
|
if err = validateGroups(routeToSave.AccessControlGroups, groupsMap); err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = am.checkRoutePrefixOrDomainsExistForPeers(account, routeToSave.Peer, routeToSave.ID, routeToSave.Copy().PeerGroups, routeToSave.Network, routeToSave.Domains)
|
if err = validateGroups(routeToSave.Groups, groupsMap); err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateGroups(routeToSave.Groups, account.Groups)
|
return groupsMap, nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
oldRoute := account.Routes[routeToSave.ID]
|
|
||||||
account.Routes[routeToSave.ID] = routeToSave
|
|
||||||
|
|
||||||
account.Network.IncSerial()
|
|
||||||
if err = am.Store.SaveAccount(ctx, account); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if am.isRouteChangeAffectPeers(account, oldRoute) || am.isRouteChangeAffectPeers(account, routeToSave) {
|
|
||||||
am.UpdateAccountPeers(ctx, accountID)
|
|
||||||
}
|
|
||||||
|
|
||||||
am.StoreEvent(ctx, userID, string(routeToSave.ID), accountID, activity.RouteUpdated, routeToSave.EventMeta())
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRoute deletes route with routeID
|
|
||||||
func (am *DefaultAccountManager) DeleteRoute(ctx context.Context, accountID string, routeID route.ID, userID string) error {
|
|
||||||
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
|
||||||
defer unlock()
|
|
||||||
|
|
||||||
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Routes, operations.Delete)
|
|
||||||
if err != nil {
|
|
||||||
return status.NewPermissionValidationError(err)
|
|
||||||
}
|
|
||||||
if !allowed {
|
|
||||||
return status.NewPermissionDeniedError()
|
|
||||||
}
|
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(ctx, accountID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
routy := account.Routes[routeID]
|
|
||||||
if routy == nil {
|
|
||||||
return status.Errorf(status.NotFound, "route with ID %s doesn't exist", routeID)
|
|
||||||
}
|
|
||||||
delete(account.Routes, routeID)
|
|
||||||
|
|
||||||
account.Network.IncSerial()
|
|
||||||
if err = am.Store.SaveAccount(ctx, account); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
am.StoreEvent(ctx, userID, string(routy.ID), accountID, activity.RouteRemoved, routy.EventMeta())
|
|
||||||
|
|
||||||
if am.isRouteChangeAffectPeers(account, routy) {
|
|
||||||
am.UpdateAccountPeers(ctx, accountID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRoutes returns a list of routes from account
|
|
||||||
func (am *DefaultAccountManager) ListRoutes(ctx context.Context, accountID, userID string) ([]*route.Route, error) {
|
|
||||||
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Routes, operations.Read)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.NewPermissionValidationError(err)
|
|
||||||
}
|
|
||||||
if !allowed {
|
|
||||||
return nil, status.NewPermissionDeniedError()
|
|
||||||
}
|
|
||||||
|
|
||||||
return am.Store.GetAccountRoutes(ctx, store.LockingStrengthShare, accountID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toProtocolRoute(route *route.Route) *proto.Route {
|
func toProtocolRoute(route *route.Route) *proto.Route {
|
||||||
@@ -455,8 +473,40 @@ func getProtoPortInfo(rule *types.RouteFirewallRule) *proto.PortInfo {
|
|||||||
return &portInfo
|
return &portInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// isRouteChangeAffectPeers checks if a given route affects peers by determining
|
// areRouteChangesAffectPeers checks if a given route affects peers by determining
|
||||||
// if it has a routing peer, distribution, or peer groups that include peers
|
// if it has a routing peer, distribution, or peer groups that include peers.
|
||||||
func (am *DefaultAccountManager) isRouteChangeAffectPeers(account *types.Account, route *route.Route) bool {
|
func areRouteChangesAffectPeers(ctx context.Context, transaction store.Store, route *route.Route) (bool, error) {
|
||||||
return am.anyGroupHasPeers(account, route.Groups) || am.anyGroupHasPeers(account, route.PeerGroups) || route.Peer != ""
|
if route.Peer != "" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPeers, err := anyGroupHasPeersOrResources(ctx, transaction, route.AccountID, route.Groups)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasPeers {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyGroupHasPeersOrResources(ctx, transaction, route.AccountID, route.PeerGroups)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoutesByPrefixOrDomains return list of routes by account and route prefix
|
||||||
|
func getRoutesByPrefixOrDomains(ctx context.Context, transaction store.Store, accountID string, prefix netip.Prefix, domains domain.List) ([]*route.Route, error) {
|
||||||
|
accountRoutes, err := transaction.GetAccountRoutes(ctx, store.LockingStrengthShare, accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
routes := make([]*route.Route, 0)
|
||||||
|
for _, r := range accountRoutes {
|
||||||
|
dynamic := r.IsDynamic()
|
||||||
|
if dynamic && r.Domains.PunycodeString() == domains.PunycodeString() ||
|
||||||
|
!dynamic && r.Network.String() == prefix.String() {
|
||||||
|
routes = append(routes, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,3 +227,7 @@ func NewUserRoleNotFoundError(role string) error {
|
|||||||
func NewOperationNotFoundError(operation operations.Operation) error {
|
func NewOperationNotFoundError(operation operations.Operation) error {
|
||||||
return Errorf(NotFound, "operation: %s not found", operation)
|
return Errorf(NotFound, "operation: %s not found", operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewRouteNotFoundError(routeID string) error {
|
||||||
|
return Errorf(NotFound, "route: %s not found", routeID)
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ import (
|
|||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/util"
|
|
||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
||||||
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
||||||
@@ -34,6 +32,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
"github.com/netbirdio/netbird/management/server/types"
|
"github.com/netbirdio/netbird/management/server/types"
|
||||||
|
"github.com/netbirdio/netbird/management/server/util"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1968,12 +1967,58 @@ func (s *SqlStore) DeletePostureChecks(ctx context.Context, lockStrength Locking
|
|||||||
|
|
||||||
// GetAccountRoutes retrieves network routes for an account.
|
// GetAccountRoutes retrieves network routes for an account.
|
||||||
func (s *SqlStore) GetAccountRoutes(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*route.Route, error) {
|
func (s *SqlStore) GetAccountRoutes(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*route.Route, error) {
|
||||||
return getRecords[*route.Route](s.db, lockStrength, accountID)
|
var routes []*route.Route
|
||||||
|
result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).
|
||||||
|
Find(&routes, accountIDCondition, accountID)
|
||||||
|
if err := result.Error; err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("failed to get routes from the store: %s", err)
|
||||||
|
return nil, status.Errorf(status.Internal, "failed to get routes from store")
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRouteByID retrieves a route by its ID and account ID.
|
// GetRouteByID retrieves a route by its ID and account ID.
|
||||||
func (s *SqlStore) GetRouteByID(ctx context.Context, lockStrength LockingStrength, routeID string, accountID string) (*route.Route, error) {
|
func (s *SqlStore) GetRouteByID(ctx context.Context, lockStrength LockingStrength, accountID string, routeID string) (*route.Route, error) {
|
||||||
return getRecordByID[route.Route](s.db, lockStrength, routeID, accountID)
|
var route *route.Route
|
||||||
|
result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).
|
||||||
|
First(&route, accountAndIDQueryCondition, accountID, routeID)
|
||||||
|
if err := result.Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, status.NewRouteNotFoundError(routeID)
|
||||||
|
}
|
||||||
|
log.WithContext(ctx).Errorf("failed to get route from the store: %s", err)
|
||||||
|
return nil, status.Errorf(status.Internal, "failed to get route from store")
|
||||||
|
}
|
||||||
|
|
||||||
|
return route, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveRoute saves a route to the database.
|
||||||
|
func (s *SqlStore) SaveRoute(ctx context.Context, lockStrength LockingStrength, route *route.Route) error {
|
||||||
|
result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).Save(route)
|
||||||
|
if err := result.Error; err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("failed to save route to the store: %s", err)
|
||||||
|
return status.Errorf(status.Internal, "failed to save route to store")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRoute deletes a route from the database.
|
||||||
|
func (s *SqlStore) DeleteRoute(ctx context.Context, lockStrength LockingStrength, accountID, routeID string) error {
|
||||||
|
result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).
|
||||||
|
Delete(&route.Route{}, accountAndIDQueryCondition, accountID, routeID)
|
||||||
|
if err := result.Error; err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("failed to delete route from the store: %s", err)
|
||||||
|
return status.Errorf(status.Internal, "failed to delete route from store")
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return status.NewRouteNotFoundError(routeID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountSetupKeys retrieves setup keys for an account.
|
// GetAccountSetupKeys retrieves setup keys for an account.
|
||||||
@@ -2104,49 +2149,6 @@ func (s *SqlStore) DeleteNameServerGroup(ctx context.Context, lockStrength Locki
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRecords retrieves records from the database based on the account ID.
|
|
||||||
func getRecords[T any](db *gorm.DB, lockStrength LockingStrength, accountID string) ([]T, error) {
|
|
||||||
tx := db
|
|
||||||
if lockStrength != LockingStrengthNone {
|
|
||||||
tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)})
|
|
||||||
}
|
|
||||||
|
|
||||||
var record []T
|
|
||||||
|
|
||||||
result := tx.Find(&record, accountIDCondition, accountID)
|
|
||||||
if err := result.Error; err != nil {
|
|
||||||
parts := strings.Split(fmt.Sprintf("%T", record), ".")
|
|
||||||
recordType := parts[len(parts)-1]
|
|
||||||
|
|
||||||
return nil, status.Errorf(status.Internal, "failed to get account %ss from store: %v", recordType, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return record, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRecordByID retrieves a record by its ID and account ID from the database.
|
|
||||||
func getRecordByID[T any](db *gorm.DB, lockStrength LockingStrength, recordID, accountID string) (*T, error) {
|
|
||||||
tx := db
|
|
||||||
if lockStrength != LockingStrengthNone {
|
|
||||||
tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)})
|
|
||||||
}
|
|
||||||
|
|
||||||
var record T
|
|
||||||
|
|
||||||
result := tx.Clauses(clause.Locking{Strength: string(lockStrength)}).
|
|
||||||
First(&record, accountAndIDQueryCondition, accountID, recordID)
|
|
||||||
if err := result.Error; err != nil {
|
|
||||||
parts := strings.Split(fmt.Sprintf("%T", record), ".")
|
|
||||||
recordType := parts[len(parts)-1]
|
|
||||||
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, status.Errorf(status.NotFound, "%s not found", recordType)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(status.Internal, "failed to get %s from store: %v", recordType, err)
|
|
||||||
}
|
|
||||||
return &record, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveDNSSettings saves the DNS settings to the store.
|
// SaveDNSSettings saves the DNS settings to the store.
|
||||||
func (s *SqlStore) SaveDNSSettings(ctx context.Context, lockStrength LockingStrength, accountID string, settings *types.DNSSettings) error {
|
func (s *SqlStore) SaveDNSSettings(ctx context.Context, lockStrength LockingStrength, accountID string, settings *types.DNSSettings) error {
|
||||||
result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).Model(&types.Account{}).
|
result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).Model(&types.Account{}).
|
||||||
|
|||||||
@@ -19,21 +19,17 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/util"
|
|
||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
||||||
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
||||||
networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
|
networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
|
||||||
"github.com/netbirdio/netbird/management/server/posture"
|
|
||||||
"github.com/netbirdio/netbird/management/server/types"
|
|
||||||
|
|
||||||
route2 "github.com/netbirdio/netbird/route"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
|
||||||
|
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
"github.com/netbirdio/netbird/management/server/posture"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
"github.com/netbirdio/netbird/management/server/types"
|
||||||
|
"github.com/netbirdio/netbird/management/server/util"
|
||||||
nbroute "github.com/netbirdio/netbird/route"
|
nbroute "github.com/netbirdio/netbird/route"
|
||||||
|
route2 "github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runTestForAllEngines(t *testing.T, testDataFile string, f func(t *testing.T, store Store)) {
|
func runTestForAllEngines(t *testing.T, testDataFile string, f func(t *testing.T, store Store)) {
|
||||||
@@ -3247,6 +3243,132 @@ func TestSqlStore_SaveGroups_LargeBatch(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 8003, len(accountGroups))
|
require.Equal(t, 8003, len(accountGroups))
|
||||||
}
|
}
|
||||||
|
func TestSqlStore_GetAccountRoutes(t *testing.T) {
|
||||||
|
store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/extended-store.sql", t.TempDir())
|
||||||
|
t.Cleanup(cleanup)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
accountID string
|
||||||
|
expectedCount int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "retrieve routes by existing account ID",
|
||||||
|
accountID: "bf1c8084-ba50-4ce7-9439-34653001fc3b",
|
||||||
|
expectedCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-existing account ID",
|
||||||
|
accountID: "nonexistent",
|
||||||
|
expectedCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty account ID",
|
||||||
|
accountID: "",
|
||||||
|
expectedCount: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
routes, err := store.GetAccountRoutes(context.Background(), LockingStrengthShare, tt.accountID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, routes, tt.expectedCount)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSqlStore_GetRouteByID(t *testing.T) {
|
||||||
|
store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/extended-store.sql", t.TempDir())
|
||||||
|
t.Cleanup(cleanup)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
accountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
routeID string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "retrieve existing route",
|
||||||
|
routeID: "ct03t427qv97vmtmglog",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "retrieve non-existing route",
|
||||||
|
routeID: "non-existing",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "retrieve with empty route ID",
|
||||||
|
routeID: "",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
route, err := store.GetRouteByID(context.Background(), LockingStrengthShare, accountID, tt.routeID)
|
||||||
|
if tt.expectError {
|
||||||
|
require.Error(t, err)
|
||||||
|
sErr, ok := status.FromError(err)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, sErr.Type(), status.NotFound)
|
||||||
|
require.Nil(t, route)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, route)
|
||||||
|
require.Equal(t, tt.routeID, string(route.ID))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSqlStore_SaveRoute(t *testing.T) {
|
||||||
|
store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/extended-store.sql", t.TempDir())
|
||||||
|
t.Cleanup(cleanup)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
accountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
|
||||||
|
|
||||||
|
route := &route2.Route{
|
||||||
|
ID: "route-id",
|
||||||
|
AccountID: accountID,
|
||||||
|
Network: netip.MustParsePrefix("10.10.0.0/16"),
|
||||||
|
NetID: "netID",
|
||||||
|
PeerGroups: []string{"routeA"},
|
||||||
|
NetworkType: route2.IPv4Network,
|
||||||
|
Masquerade: true,
|
||||||
|
Metric: 9999,
|
||||||
|
Enabled: true,
|
||||||
|
Groups: []string{"groupA"},
|
||||||
|
AccessControlGroups: []string{},
|
||||||
|
}
|
||||||
|
err = store.SaveRoute(context.Background(), LockingStrengthUpdate, route)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
saveRoute, err := store.GetRouteByID(context.Background(), LockingStrengthShare, accountID, string(route.ID))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, route, saveRoute)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSqlStore_DeleteRoute(t *testing.T) {
|
||||||
|
store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/extended-store.sql", t.TempDir())
|
||||||
|
t.Cleanup(cleanup)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
accountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
|
||||||
|
routeID := "ct03t427qv97vmtmglog"
|
||||||
|
|
||||||
|
err = store.DeleteRoute(context.Background(), LockingStrengthUpdate, accountID, routeID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
route, err := store.GetRouteByID(context.Background(), LockingStrengthShare, accountID, routeID)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, route)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSqlStore_GetAccountMeta(t *testing.T) {
|
func TestSqlStore_GetAccountMeta(t *testing.T) {
|
||||||
store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/extended-store.sql", t.TempDir())
|
store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/extended-store.sql", t.TempDir())
|
||||||
|
|||||||
@@ -145,7 +145,9 @@ type Store interface {
|
|||||||
DeleteSetupKey(ctx context.Context, lockStrength LockingStrength, accountID, keyID string) error
|
DeleteSetupKey(ctx context.Context, lockStrength LockingStrength, accountID, keyID string) error
|
||||||
|
|
||||||
GetAccountRoutes(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*route.Route, error)
|
GetAccountRoutes(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*route.Route, error)
|
||||||
GetRouteByID(ctx context.Context, lockStrength LockingStrength, routeID string, accountID string) (*route.Route, error)
|
GetRouteByID(ctx context.Context, lockStrength LockingStrength, accountID, routeID string) (*route.Route, error)
|
||||||
|
SaveRoute(ctx context.Context, lockStrength LockingStrength, route *route.Route) error
|
||||||
|
DeleteRoute(ctx context.Context, lockStrength LockingStrength, accountID, routeID string) error
|
||||||
|
|
||||||
GetAccountNameServerGroups(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*dns.NameServerGroup, error)
|
GetAccountNameServerGroups(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*dns.NameServerGroup, error)
|
||||||
GetNameServerGroupByID(ctx context.Context, lockStrength LockingStrength, nameServerGroupID string, accountID string) (*dns.NameServerGroup, error)
|
GetNameServerGroupByID(ctx context.Context, lockStrength LockingStrength, nameServerGroupID string, accountID string) (*dns.NameServerGroup, error)
|
||||||
|
|||||||
@@ -38,4 +38,5 @@ INSERT INTO "groups" VALUES('cfefqs706sqkneg59g2g','bf1c8084-ba50-4ce7-9439-3465
|
|||||||
INSERT INTO posture_checks VALUES('csplshq7qv948l48f7t0','NetBird Version > 0.32.0','','bf1c8084-ba50-4ce7-9439-34653001fc3b','{"NBVersionCheck":{"MinVersion":"0.31.0"}}');
|
INSERT INTO posture_checks VALUES('csplshq7qv948l48f7t0','NetBird Version > 0.32.0','','bf1c8084-ba50-4ce7-9439-34653001fc3b','{"NBVersionCheck":{"MinVersion":"0.31.0"}}');
|
||||||
INSERT INTO posture_checks VALUES('cspnllq7qv95uq1r4k90','Allow Berlin and Deny local network 172.16.1.0/24','','bf1c8084-ba50-4ce7-9439-34653001fc3b','{"GeoLocationCheck":{"Locations":[{"CountryCode":"DE","CityName":"Berlin"}],"Action":"allow"},"PeerNetworkRangeCheck":{"Action":"deny","Ranges":["172.16.1.0/24"]}}');
|
INSERT INTO posture_checks VALUES('cspnllq7qv95uq1r4k90','Allow Berlin and Deny local network 172.16.1.0/24','','bf1c8084-ba50-4ce7-9439-34653001fc3b','{"GeoLocationCheck":{"Locations":[{"CountryCode":"DE","CityName":"Berlin"}],"Action":"allow"},"PeerNetworkRangeCheck":{"Action":"deny","Ranges":["172.16.1.0/24"]}}');
|
||||||
INSERT INTO name_server_groups VALUES('csqdelq7qv97ncu7d9t0','bf1c8084-ba50-4ce7-9439-34653001fc3b','Google DNS','Google DNS Servers','[{"IP":"8.8.8.8","NSType":1,"Port":53},{"IP":"8.8.4.4","NSType":1,"Port":53}]','["cfefqs706sqkneg59g2g"]',1,'[]',1,0);
|
INSERT INTO name_server_groups VALUES('csqdelq7qv97ncu7d9t0','bf1c8084-ba50-4ce7-9439-34653001fc3b','Google DNS','Google DNS Servers','[{"IP":"8.8.8.8","NSType":1,"Port":53},{"IP":"8.8.4.4","NSType":1,"Port":53}]','["cfefqs706sqkneg59g2g"]',1,'[]',1,0);
|
||||||
|
INSERT INTO routes VALUES('ct03t427qv97vmtmglog','bf1c8084-ba50-4ce7-9439-34653001fc3b','"10.10.0.0/16"',NULL,0,'aws-eu-central-1-vpc','Production VPC in Frankfurt','ct03r5q7qv97vmtmglng',NULL,1,1,9999,1,'["cfefqs706sqkneg59g2g"]',NULL);
|
||||||
INSERT INTO installations VALUES(1,'');
|
INSERT INTO installations VALUES(1,'');
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ const (
|
|||||||
PublicCategory = "public"
|
PublicCategory = "public"
|
||||||
PrivateCategory = "private"
|
PrivateCategory = "private"
|
||||||
UnknownCategory = "unknown"
|
UnknownCategory = "unknown"
|
||||||
|
|
||||||
|
// firewallRuleMinPortRangesVer defines the minimum peer version that supports port range rules.
|
||||||
|
firewallRuleMinPortRangesVer = "0.48.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LookupMap map[string]struct{}
|
type LookupMap map[string]struct{}
|
||||||
@@ -248,7 +251,7 @@ func (a *Account) GetPeerNetworkMap(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aclPeers, firewallRules := a.GetPeerConnectionResources(ctx, peerID, validatedPeersMap)
|
aclPeers, firewallRules := a.GetPeerConnectionResources(ctx, peer, validatedPeersMap)
|
||||||
// exclude expired peers
|
// exclude expired peers
|
||||||
var peersToConnect []*nbpeer.Peer
|
var peersToConnect []*nbpeer.Peer
|
||||||
var expiredPeers []*nbpeer.Peer
|
var expiredPeers []*nbpeer.Peer
|
||||||
@@ -961,8 +964,9 @@ func (a *Account) UserGroupsRemoveFromPeers(userID string, groups ...string) map
|
|||||||
// GetPeerConnectionResources for a given peer
|
// GetPeerConnectionResources for a given peer
|
||||||
//
|
//
|
||||||
// This function returns the list of peers and firewall rules that are applicable to a given peer.
|
// This function returns the list of peers and firewall rules that are applicable to a given peer.
|
||||||
func (a *Account) GetPeerConnectionResources(ctx context.Context, peerID string, validatedPeersMap map[string]struct{}) ([]*nbpeer.Peer, []*FirewallRule) {
|
func (a *Account) GetPeerConnectionResources(ctx context.Context, peer *nbpeer.Peer, validatedPeersMap map[string]struct{}) ([]*nbpeer.Peer, []*FirewallRule) {
|
||||||
generateResources, getAccumulatedResources := a.connResourcesGenerator(ctx)
|
generateResources, getAccumulatedResources := a.connResourcesGenerator(ctx, peer)
|
||||||
|
|
||||||
for _, policy := range a.Policies {
|
for _, policy := range a.Policies {
|
||||||
if !policy.Enabled {
|
if !policy.Enabled {
|
||||||
continue
|
continue
|
||||||
@@ -973,8 +977,8 @@ func (a *Account) GetPeerConnectionResources(ctx context.Context, peerID string,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sourcePeers, peerInSources := a.getAllPeersFromGroups(ctx, rule.Sources, peerID, policy.SourcePostureChecks, validatedPeersMap)
|
sourcePeers, peerInSources := a.getAllPeersFromGroups(ctx, rule.Sources, peer.ID, policy.SourcePostureChecks, validatedPeersMap)
|
||||||
destinationPeers, peerInDestinations := a.getAllPeersFromGroups(ctx, rule.Destinations, peerID, nil, validatedPeersMap)
|
destinationPeers, peerInDestinations := a.getAllPeersFromGroups(ctx, rule.Destinations, peer.ID, nil, validatedPeersMap)
|
||||||
|
|
||||||
if rule.Bidirectional {
|
if rule.Bidirectional {
|
||||||
if peerInSources {
|
if peerInSources {
|
||||||
@@ -1003,7 +1007,7 @@ func (a *Account) GetPeerConnectionResources(ctx context.Context, peerID string,
|
|||||||
// The generator function is used to generate the list of peers and firewall rules that are applicable to a given peer.
|
// The generator function is used to generate the list of peers and firewall rules that are applicable to a given peer.
|
||||||
// It safe to call the generator function multiple times for same peer and different rules no duplicates will be
|
// It safe to call the generator function multiple times for same peer and different rules no duplicates will be
|
||||||
// generated. The accumulator function returns the result of all the generator calls.
|
// generated. The accumulator function returns the result of all the generator calls.
|
||||||
func (a *Account) connResourcesGenerator(ctx context.Context) (func(*PolicyRule, []*nbpeer.Peer, int), func() ([]*nbpeer.Peer, []*FirewallRule)) {
|
func (a *Account) connResourcesGenerator(ctx context.Context, targetPeer *nbpeer.Peer) (func(*PolicyRule, []*nbpeer.Peer, int), func() ([]*nbpeer.Peer, []*FirewallRule)) {
|
||||||
rulesExists := make(map[string]struct{})
|
rulesExists := make(map[string]struct{})
|
||||||
peersExists := make(map[string]struct{})
|
peersExists := make(map[string]struct{})
|
||||||
rules := make([]*FirewallRule, 0)
|
rules := make([]*FirewallRule, 0)
|
||||||
@@ -1051,17 +1055,7 @@ func (a *Account) connResourcesGenerator(ctx context.Context) (func(*PolicyRule,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, port := range rule.Ports {
|
rules = append(rules, expandPortsAndRanges(fr, rule, targetPeer)...)
|
||||||
pr := fr // clone rule and add set new port
|
|
||||||
pr.Port = port
|
|
||||||
rules = append(rules, &pr)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, portRange := range rule.PortRanges {
|
|
||||||
pr := fr
|
|
||||||
pr.PortRange = portRange
|
|
||||||
rules = append(rules, &pr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, func() ([]*nbpeer.Peer, []*FirewallRule) {
|
}, func() ([]*nbpeer.Peer, []*FirewallRule) {
|
||||||
return peers, rules
|
return peers, rules
|
||||||
@@ -1590,3 +1584,45 @@ func (a *Account) AddAllGroup() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expandPortsAndRanges expands Ports and PortRanges of a rule into individual firewall rules
|
||||||
|
func expandPortsAndRanges(base FirewallRule, rule *PolicyRule, peer *nbpeer.Peer) []*FirewallRule {
|
||||||
|
var expanded []*FirewallRule
|
||||||
|
|
||||||
|
if len(rule.Ports) > 0 {
|
||||||
|
for _, port := range rule.Ports {
|
||||||
|
fr := base
|
||||||
|
fr.Port = port
|
||||||
|
expanded = append(expanded, &fr)
|
||||||
|
}
|
||||||
|
return expanded
|
||||||
|
}
|
||||||
|
|
||||||
|
supportPortRanges := peerSupportsPortRanges(peer.Meta.WtVersion)
|
||||||
|
for _, portRange := range rule.PortRanges {
|
||||||
|
fr := base
|
||||||
|
|
||||||
|
if supportPortRanges {
|
||||||
|
fr.PortRange = portRange
|
||||||
|
} else {
|
||||||
|
// Peer doesn't support port ranges, only allow single-port ranges
|
||||||
|
if portRange.Start != portRange.End {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fr.Port = strconv.FormatUint(uint64(portRange.Start), 10)
|
||||||
|
}
|
||||||
|
expanded = append(expanded, &fr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return expanded
|
||||||
|
}
|
||||||
|
|
||||||
|
// peerSupportsPortRanges checks if the peer version supports port ranges.
|
||||||
|
func peerSupportsPortRanges(peerVer string) bool {
|
||||||
|
if strings.Contains(peerVer, "dev") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
meetMinVer, err := posture.MeetsMinVersion(firewallRuleMinPortRangesVer, peerVer)
|
||||||
|
return err == nil && meetMinVer
|
||||||
|
}
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ func generateRouteFirewallRules(ctx context.Context, route *nbroute.Route, rule
|
|||||||
rules = append(rules, generateRulesWithPortRanges(baseRule, rule, rulesExists)...)
|
rules = append(rules, generateRulesWithPortRanges(baseRule, rule, rulesExists)...)
|
||||||
} else {
|
} else {
|
||||||
rules = append(rules, generateRulesWithPorts(ctx, baseRule, rule, rulesExists)...)
|
rules = append(rules, generateRulesWithPorts(ctx, baseRule, rule, rulesExists)...)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: generate IPv6 rules for dynamic routes
|
// TODO: generate IPv6 rules for dynamic routes
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ var (
|
|||||||
// Update fetch the version info periodically and notify the onUpdateListener in case the UI version or the
|
// Update fetch the version info periodically and notify the onUpdateListener in case the UI version or the
|
||||||
// daemon version are deprecated
|
// daemon version are deprecated
|
||||||
type Update struct {
|
type Update struct {
|
||||||
|
httpAgent string
|
||||||
uiVersion *goversion.Version
|
uiVersion *goversion.Version
|
||||||
daemonVersion *goversion.Version
|
daemonVersion *goversion.Version
|
||||||
latestAvailable *goversion.Version
|
latestAvailable *goversion.Version
|
||||||
@@ -34,7 +35,7 @@ type Update struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewUpdate instantiate Update and start to fetch the new version information
|
// NewUpdate instantiate Update and start to fetch the new version information
|
||||||
func NewUpdate() *Update {
|
func NewUpdate(httpAgent string) *Update {
|
||||||
currentVersion, err := goversion.NewVersion(version)
|
currentVersion, err := goversion.NewVersion(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
currentVersion, _ = goversion.NewVersion("0.0.0")
|
currentVersion, _ = goversion.NewVersion("0.0.0")
|
||||||
@@ -43,6 +44,7 @@ func NewUpdate() *Update {
|
|||||||
latestAvailable, _ := goversion.NewVersion("0.0.0")
|
latestAvailable, _ := goversion.NewVersion("0.0.0")
|
||||||
|
|
||||||
u := &Update{
|
u := &Update{
|
||||||
|
httpAgent: httpAgent,
|
||||||
latestAvailable: latestAvailable,
|
latestAvailable: latestAvailable,
|
||||||
uiVersion: currentVersion,
|
uiVersion: currentVersion,
|
||||||
fetchTicker: time.NewTicker(fetchPeriod),
|
fetchTicker: time.NewTicker(fetchPeriod),
|
||||||
@@ -112,7 +114,15 @@ func (u *Update) startFetcher() {
|
|||||||
func (u *Update) fetchVersion() bool {
|
func (u *Update) fetchVersion() bool {
|
||||||
log.Debugf("fetching version info from %s", versionURL)
|
log.Debugf("fetching version info from %s", versionURL)
|
||||||
|
|
||||||
resp, err := http.Get(versionURL)
|
req, err := http.NewRequest("GET", versionURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to create request for version info: %s", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", u.httpAgent)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to fetch version info: %s", err)
|
log.Errorf("failed to fetch version info: %s", err)
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const httpAgent = "pkg/test"
|
||||||
|
|
||||||
func TestNewUpdate(t *testing.T) {
|
func TestNewUpdate(t *testing.T) {
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -21,7 +23,7 @@ func TestNewUpdate(t *testing.T) {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
onUpdate := false
|
onUpdate := false
|
||||||
u := NewUpdate()
|
u := NewUpdate(httpAgent)
|
||||||
defer u.StopWatch()
|
defer u.StopWatch()
|
||||||
u.SetOnUpdateListener(func() {
|
u.SetOnUpdateListener(func() {
|
||||||
onUpdate = true
|
onUpdate = true
|
||||||
@@ -46,7 +48,7 @@ func TestDoNotUpdate(t *testing.T) {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
onUpdate := false
|
onUpdate := false
|
||||||
u := NewUpdate()
|
u := NewUpdate(httpAgent)
|
||||||
defer u.StopWatch()
|
defer u.StopWatch()
|
||||||
u.SetOnUpdateListener(func() {
|
u.SetOnUpdateListener(func() {
|
||||||
onUpdate = true
|
onUpdate = true
|
||||||
@@ -71,7 +73,7 @@ func TestDaemonUpdate(t *testing.T) {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
onUpdate := false
|
onUpdate := false
|
||||||
u := NewUpdate()
|
u := NewUpdate(httpAgent)
|
||||||
defer u.StopWatch()
|
defer u.StopWatch()
|
||||||
u.SetOnUpdateListener(func() {
|
u.SetOnUpdateListener(func() {
|
||||||
onUpdate = true
|
onUpdate = true
|
||||||
|
|||||||
Reference in New Issue
Block a user