Compare commits

..

7 Commits

Author SHA1 Message Date
Maycon Santos
d9fa28d8a0 test postgres 2023-08-02 18:56:06 +02:00
Maycon Santos
144ac868e0 create empty files 2023-08-02 18:32:31 +02:00
Maycon Santos
b70339d3bd use check_jq 2023-08-02 12:32:43 +02:00
Maycon Santos
ee890971a3 update texts and expose errors to stderr 2023-08-02 12:19:35 +02:00
Maycon Santos
eeb1b619b7 Automate Zitadel IDP configuration (#1006)
Add automated zitadel configuration and netbird using a single script

run infra files test only when they change or when pushed to main

run releases only on changes to related files or when pushed to main

push install and automated getting started script to releases page
2023-08-01 23:31:14 +02:00
Maycon Santos
4b47f6b23c Merge branch 'main' into update-getting-started-flow 2023-07-30 16:39:49 +02:00
Maycon Santos
5883e019c9 update readme 2023-07-07 17:05:21 +02:00
179 changed files with 2687 additions and 4384 deletions

View File

@@ -1,41 +0,0 @@
name: Android build validation
on:
push:
branches:
- main
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: "1.20.x"
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: NDK Cache
id: ndk-cache
uses: actions/cache@v3
with:
path: /usr/local/lib/android/sdk/ndk
key: ndk-cache-23.1.7779620
- name: Setup NDK
run: /usr/local/lib/android/sdk/tools/bin/sdkmanager --install "ndk;23.1.7779620"
- name: install gomobile
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20230531173138-3c911d8e3eda
- name: gomobile init
run: gomobile init
- name: build android nebtird 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

View File

@@ -15,14 +15,14 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- name: Install Go - name: Install Go
uses: actions/setup-go@v4 uses: actions/setup-go@v2
with: with:
go-version: "1.20.x" go-version: "1.20.x"
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Cache Go modules - name: Cache Go modules
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: macos-go-${{ hashFiles('**/go.sum') }} key: macos-go-${{ hashFiles('**/go.sum') }}

View File

@@ -18,13 +18,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Install Go - name: Install Go
uses: actions/setup-go@v4 uses: actions/setup-go@v2
with: with:
go-version: "1.20.x" go-version: "1.20.x"
- name: Cache Go modules - name: Cache Go modules
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
@@ -32,7 +32,7 @@ jobs:
${{ runner.os }}-go- ${{ runner.os }}-go-
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Install dependencies - name: Install dependencies
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib
@@ -47,13 +47,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Install Go - name: Install Go
uses: actions/setup-go@v4 uses: actions/setup-go@v2
with: with:
go-version: "1.20.x" go-version: "1.20.x"
- name: Cache Go modules - name: Cache Go modules
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
@@ -61,7 +61,7 @@ jobs:
${{ runner.os }}-go- ${{ runner.os }}-go-
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Install dependencies - name: Install dependencies
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev

View File

@@ -8,13 +8,14 @@ jobs:
name: lint name: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install Go - name: Install Go
uses: actions/setup-go@v4 uses: actions/setup-go@v2
with: with:
go-version: "1.20.x" go-version: "1.20.x"
- name: Install dependencies - name: Install dependencies
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v3 uses: golangci/golangci-lint-action@v2
with:
args: --timeout=6m

View File

@@ -1,36 +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:
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@v3
- 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

View File

@@ -0,0 +1,60 @@
name: Test installation Darwin
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:
install-cli-only:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Rename brew package
if: ${{ matrix.check_bin_install }}
run: mv /opt/homebrew/bin/brew /opt/homebrew/bin/brew.bak
- name: Run install script
run: |
sh ./release_files/install.sh
env:
SKIP_UI_APP: true
- name: Run tests
run: |
if ! command -v netbird &> /dev/null; then
echo "Error: netbird is not installed"
exit 1
fi
install-all:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Rename brew package
if: ${{ matrix.check_bin_install }}
run: mv /opt/homebrew/bin/brew /opt/homebrew/bin/brew.bak
- name: Run install script
run: |
sh ./release_files/install.sh
- name: Run tests
run: |
if ! command -v netbird &> /dev/null; then
echo "Error: netbird is not installed"
exit 1
fi
if [[ $(mdfind "kMDItemContentType == 'com.apple.application-bundle' && kMDItemFSName == '*NetBird UI.app'") ]]; then
echo "Error: NetBird UI is not installed"
exit 1
fi

View File

@@ -0,0 +1,38 @@
name: Test installation Linux
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:
install-cli-only:
runs-on: ubuntu-latest
strategy:
matrix:
check_bin_install: [true, false]
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Rename apt package
if: ${{ matrix.check_bin_install }}
run: |
sudo mv /usr/bin/apt /usr/bin/apt.bak
sudo mv /usr/bin/apt-get /usr/bin/apt-get.bak
- name: Run install script
run: |
sh ./release_files/install.sh
- name: Run tests
run: |
if ! command -v netbird &> /dev/null; then
echo "Error: netbird is not installed"
exit 1
fi

View File

@@ -19,7 +19,7 @@ on:
- '**/Dockerfile.*' - '**/Dockerfile.*'
env: env:
SIGN_PIPE_VER: "v0.0.9" SIGN_PIPE_VER: "v0.0.8"
GORELEASER_VER: "v1.14.1" GORELEASER_VER: "v1.14.1"
concurrency: concurrency:
@@ -29,24 +29,20 @@ concurrency:
jobs: jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
flags: ""
steps: steps:
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
run: echo "flags=--snapshot" >> $GITHUB_ENV
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
fetch-depth: 0 # It is required for GoReleaser to work properly fetch-depth: 0 # It is required for GoReleaser to work properly
- -
name: Set up Go name: Set up Go
uses: actions/setup-go@v4 uses: actions/setup-go@v2
with: with:
go-version: "1.20" go-version: "1.20"
- -
name: Cache Go modules name: Cache Go modules
uses: actions/cache@v3 uses: actions/cache@v1
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
@@ -60,10 +56,10 @@ jobs:
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@v1
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v1
- -
name: Login to Docker hub name: Login to Docker hub
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
@@ -86,10 +82,10 @@ jobs:
run: rsrc -arch 386 -ico client/ui/netbird.ico -manifest client/manifest.xml -o client/resources_windows_386.syso run: rsrc -arch 386 -ico client/ui/netbird.ico -manifest client/manifest.xml -o client/resources_windows_386.syso
- -
name: Run GoReleaser name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4 uses: goreleaser/goreleaser-action@v2
with: with:
version: ${{ env.GORELEASER_VER }} version: ${{ env.GORELEASER_VER }}
args: release --rm-dist ${{ env.flags }} args: release --rm-dist
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
@@ -97,7 +93,7 @@ jobs:
UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }} UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
- -
name: upload non tags for debug purposes name: upload non tags for debug purposes
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: release name: release
path: dist/ path: dist/
@@ -106,19 +102,17 @@ jobs:
release_ui: release_ui:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
run: echo "flags=--snapshot" >> $GITHUB_ENV
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
fetch-depth: 0 # It is required for GoReleaser to work properly fetch-depth: 0 # It is required for GoReleaser to work properly
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v4 uses: actions/setup-go@v2
with: with:
go-version: "1.20" go-version: "1.20"
- name: Cache Go modules - name: Cache Go modules
uses: actions/cache@v3 uses: actions/cache@v1
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-ui-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-ui-go-${{ hashFiles('**/go.sum') }}
@@ -138,17 +132,17 @@ jobs:
- name: Generate windows rsrc - name: Generate windows rsrc
run: rsrc -arch amd64 -ico client/ui/netbird.ico -manifest client/ui/manifest.xml -o client/ui/resources_windows_amd64.syso run: rsrc -arch amd64 -ico client/ui/netbird.ico -manifest client/ui/manifest.xml -o client/ui/resources_windows_amd64.syso
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4 uses: goreleaser/goreleaser-action@v2
with: with:
version: ${{ env.GORELEASER_VER }} version: ${{ env.GORELEASER_VER }}
args: release --config .goreleaser_ui.yaml --rm-dist ${{ env.flags }} args: release --config .goreleaser_ui.yaml --rm-dist
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
UPLOAD_DEBIAN_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }} UPLOAD_DEBIAN_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }} UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
- name: upload non tags for debug purposes - name: upload non tags for debug purposes
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: release-ui name: release-ui
path: dist/ path: dist/
@@ -157,21 +151,19 @@ jobs:
release_ui_darwin: release_ui_darwin:
runs-on: macos-11 runs-on: macos-11
steps: steps:
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
run: echo "flags=--snapshot" >> $GITHUB_ENV
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2
with: with:
fetch-depth: 0 # It is required for GoReleaser to work properly fetch-depth: 0 # It is required for GoReleaser to work properly
- -
name: Set up Go name: Set up Go
uses: actions/setup-go@v4 uses: actions/setup-go@v2
with: with:
go-version: "1.20" go-version: "1.20"
- -
name: Cache Go modules name: Cache Go modules
uses: actions/cache@v3 uses: actions/cache@v1
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-ui-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-ui-go-${{ hashFiles('**/go.sum') }}
@@ -183,15 +175,15 @@ jobs:
- -
name: Run GoReleaser name: Run GoReleaser
id: goreleaser id: goreleaser
uses: goreleaser/goreleaser-action@v4 uses: goreleaser/goreleaser-action@v2
with: with:
version: ${{ env.GORELEASER_VER }} version: ${{ env.GORELEASER_VER }}
args: release --config .goreleaser_ui_darwin.yaml --rm-dist ${{ env.flags }} args: release --config .goreleaser_ui_darwin.yaml --rm-dist
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- -
name: upload non tags for debug purposes name: upload non tags for debug purposes
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v2
with: with:
name: release-ui-darwin name: release-ui-darwin
path: dist/ path: dist/

View File

@@ -9,6 +9,7 @@ on:
- 'infrastructure_files/**' - 'infrastructure_files/**'
- '.github/workflows/test-infrastructure-files.yml' - '.github/workflows/test-infrastructure-files.yml'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
cancel-in-progress: true cancel-in-progress: true
@@ -24,12 +25,12 @@ jobs:
run: sudo apt-get install -y curl run: sudo apt-get install -y curl
- name: Install Go - name: Install Go
uses: actions/setup-go@v4 uses: actions/setup-go@v2
with: with:
go-version: "1.20.x" go-version: "1.20.x"
- name: Cache Go modules - name: Cache Go modules
uses: actions/cache@v3 uses: actions/cache@v2
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
@@ -80,7 +81,6 @@ jobs:
CI_NETBIRD_MGMT_IDP: "none" CI_NETBIRD_MGMT_IDP: "none"
CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
CI_NETBIRD_SIGNAL_PORT: 12345
run: | run: |
grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID
@@ -92,7 +92,6 @@ jobs:
grep NETBIRD_MGMT_API_ENDPOINT docker-compose.yml | grep "$CI_NETBIRD_DOMAIN:33073" 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_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 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 LETSENCRYPT_DOMAIN docker-compose.yml | egrep 'LETSENCRYPT_DOMAIN=$'
grep NETBIRD_TOKEN_SOURCE docker-compose.yml | grep $CI_NETBIRD_TOKEN_SOURCE grep NETBIRD_TOKEN_SOURCE docker-compose.yml | grep $CI_NETBIRD_TOKEN_SOURCE
grep AuthUserIDClaim management.json | grep $CI_NETBIRD_AUTH_USER_ID_CLAIM grep AuthUserIDClaim management.json | grep $CI_NETBIRD_AUTH_USER_ID_CLAIM
@@ -122,7 +121,7 @@ jobs:
- name: test running containers - name: test running containers
run: | run: |
count=$(docker compose ps --format json | jq '. | select(.Name | contains("infrastructure_files")) | .State' | grep -c running) count=$(docker compose ps --format json | jq '.[] | select(.Project | contains("infrastructure_files")) | .State' | grep -c running)
test $count -eq 4 test $count -eq 4
working-directory: infrastructure_files working-directory: infrastructure_files
@@ -136,7 +135,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: run script - name: run script
run: NETBIRD_DOMAIN=use-ip bash -x infrastructure_files/getting-started-with-zitadel.sh run: bash -x infrastructure_files/getting-started-with-zitadel.sh
- name: test Caddy file gen - name: test Caddy file gen
run: test -f Caddyfile run: test -f Caddyfile

1
.gitignore vendored
View File

@@ -19,4 +19,3 @@ client/.distfiles/
infrastructure_files/setup.env infrastructure_files/setup.env
infrastructure_files/setup-*.env infrastructure_files/setup-*.env
.vscode .vscode
.DS_Store

View File

@@ -1,54 +0,0 @@
run:
# Timeout for analysis, e.g. 30s, 5m.
# Default: 1m
timeout: 6m
# This file contains only configs which differ from defaults.
# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml
linters-settings:
errcheck:
# Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
# Such cases aren't reported by default.
# Default: false
check-type-assertions: false
govet:
# Enable all analyzers.
# Default: false
enable-all: false
enable:
- nilness
linters:
disable-all: true
enable:
## enabled by default
- errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases
- gosimple # specializes in simplifying a code
- govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
- ineffassign # detects when assignments to existing variables are not used
- staticcheck # is a go vet on steroids, applying a ton of static analysis checks
- typecheck # like the front-end of a Go compiler, parses and type-checks Go code
- unused # checks for unused constants, variables, functions and types
## disable by default but the have interesting results so lets add them
- bodyclose # checks whether HTTP response body is closed successfully
- nilerr # finds the code that returns nil even if it checks that the error is not nil
- nilnil # checks that there is no simultaneous return of nil error and an invalid value
- sqlclosecheck # checks that sql.Rows and sql.Stmt are closed
- wastedassign # wastedassign finds wasted assignment statements
issues:
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 5
exclude-rules:
- path: sharedsock/filter.go
linters:
- unused
- path: client/firewall/iptables/rule.go
linters:
- unused
- path: mock.go
linters:
- nilnil

View File

@@ -378,11 +378,6 @@ uploads:
username: dev@wiretrustee.com username: dev@wiretrustee.com
method: PUT method: PUT
checksum:
extra_files:
- glob: ./infrastructure_files/getting-started-with-zitadel.sh
- glob: ./release_files/install.sh
release: release:
extra_files: extra_files:
- glob: ./infrastructure_files/getting-started-with-zitadel.sh - glob: ./infrastructure_files/getting-started-with-zitadel.sh

View File

@@ -1,6 +1,6 @@
<p align="center"> <p align="center">
<strong>:hatching_chick: New Release! Self-hosting in under 5 min.</strong> <strong>:hatching_chick: New Release! Peer expiration.</strong>
<a href="https://github.com/netbirdio/netbird#quickstart-with-self-hosted-netbird"> <a href="https://github.com/netbirdio/netbird/releases">
Learn more Learn more
</a> </a>
</p> </p>
@@ -24,7 +24,7 @@
<p align="center"> <p align="center">
<strong> <strong>
Start using NetBird at <a href="https://netbird.io/pricing">netbird.io</a> Start using NetBird at <a href="https://app.netbird.io/">app.netbird.io</a>
<br/> <br/>
See <a href="https://netbird.io/docs/">Documentation</a> See <a href="https://netbird.io/docs/">Documentation</a>
<br/> <br/>
@@ -36,62 +36,48 @@
<br> <br>
**NetBird combines a configuration-free peer-to-peer private network and a centralized access control system in a single platform, making it easy to create secure private networks for your organization or home.** **NetBird is an open-source VPN management platform built on top of WireGuard® making it easy to create secure private networks for your organization or home.**
**Connect.** NetBird creates a WireGuard-based overlay network that automatically connects your machines over an encrypted tunnel, leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth. It requires zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth.
**Secure.** NetBird enables secure remote access by applying granular access policies, while allowing you to manage them intuitively from a single place. Works universally on any infrastructure. NetBird uses [NAT traversal techniques](https://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment) to automatically create an overlay peer-to-peer network connecting machines regardless of location (home, office, data center, container, cloud, or edge environments), unifying virtual private network management experience.
**Key features:**
- \[x] Automatic IP allocation and network management with a Web UI ([separate repo](https://github.com/netbirdio/dashboard))
- \[x] Automatic WireGuard peer (machine) discovery and configuration.
- \[x] Encrypted peer-to-peer connections without a central VPN gateway.
- \[x] Connection relay fallback in case a peer-to-peer connection is not possible.
- \[x] Desktop client applications for Linux, MacOS, and Windows (systray).
- \[x] Multiuser support - sharing network between multiple users.
- \[x] SSO and MFA support.
- \[x] Multicloud and hybrid-cloud support.
- \[x] Kernel WireGuard usage when possible.
- \[x] Access Controls - groups & rules.
- \[x] Remote SSH access without managing SSH keys.
- \[x] Network Routes.
- \[x] Private DNS.
- \[x] Network Activity Monitoring.
- \[x] Mobile clients (Android).
-
**Coming soon:**
- \[ ] Mobile clients (iOS).
### Secure peer-to-peer VPN with SSO and MFA in minutes ### Secure peer-to-peer VPN with SSO and MFA in minutes
https://user-images.githubusercontent.com/700848/197345890-2e2cded5-7b7a-436f-a444-94e80dd24f46.mov https://user-images.githubusercontent.com/700848/197345890-2e2cded5-7b7a-436f-a444-94e80dd24f46.mov
### Key features **Note**: The `main` branch may be in an *unstable or even broken state* during development.
For stable versions, see [releases](https://github.com/netbirdio/netbird/releases).
| Connectivity | Management | Automation | Platforms | ### Start using NetBird
|-------------------------------------------------------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------------|---------------------------------------| - Hosted version: [https://app.netbird.io/](https://app.netbird.io/).
| <ul><li> - \[x] Kernel WireGuard </ul></li> | <ul><li> - \[x] [Admin Web UI](https://github.com/netbirdio/dashboard) </ul></li> | <ul><li> - \[x] [Public API](https://docs.netbird.io/api) </ul></li> | <ul><li> - \[x] Linux </ul></li> | - See our documentation for [Quickstart Guide](https://docs.netbird.io/how-to/getting-started).
| <ul><li> - \[x] Peer-to-peer connections </ul></li> | <ul><li> - \[x] Auto peer discovery and configuration </ul></li> | <ul><li> - \[x] [Setup keys for bulk network provisioning](https://docs.netbird.io/how-to/register-machines-using-setup-keys) </ul></li> | <ul><li> - \[x] Mac </ul></li> | - If you are looking to self-host NetBird, check our [Self-Hosting Guide](https://docs.netbird.io/selfhosted/selfhosted-guide).
| <ul><li> - \[x] Peer-to-peer encryption </ul></li> | <ul><li> - \[x] [IdP integrations](https://docs.netbird.io/selfhosted/identity-providers) </ul></li> | <ul><li> - \[x] [Self-hosting quickstart script](https://docs.netbird.io/selfhosted/selfhosted-quickstart) </ul></li> | <ul><li> - \[x] Windows </ul></li> | - Step-by-step [Installation Guide](https://docs.netbird.io/how-to/getting-started#installation) for different platforms.
| <ul><li> - \[x] Connection relay fallback </ul></li> | <ul><li> - \[x] [SSO & MFA support](https://docs.netbird.io/how-to/installation#running-net-bird-with-sso-login) </ul></li> | <ul><li> - \[x] IdP groups sync with JWT </ul></li> | <ul><li> - \[x] Android </ul></li> | - Web UI [repository](https://github.com/netbirdio/dashboard).
| <ul><li> - \[x] [Routes to external networks](https://docs.netbird.io/how-to/routing-traffic-to-private-networks) </ul></li> | <ul><li> - \[x] [Access control - groups & rules](https://docs.netbird.io/how-to/manage-network-access) </ul></li> | | <ul><li> - \[ ] iOS </ul></li> | - 5 min [demo video](https://youtu.be/Tu9tPsUWaY0) on YouTube.
| <ul><li> - \[x] NAT traversal with BPF </ul></li> | <ul><li> - \[x] [Private DNS](https://docs.netbird.io/how-to/manage-dns-in-your-network) </ul></li> | | <ul><li> - \[x] Docker </ul></li> |
| | <ul><li> - \[x] [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network) </ul></li> | | <ul><li> - \[x] OpenWRT </ul></li> |
| | <ul><li> - \[x] [Activity logging](https://docs.netbird.io/how-to/monitor-system-and-network-activity) </ul></li> | | |
| | <ul><li> - \[x] SSH access management </ul></li> | | |
### Quickstart with NetBird Cloud
- Download and install NetBird at [https://app.netbird.io/install](https://app.netbird.io/install)
- Follow the steps to sign-up with Google, Microsoft, GitHub or your email address.
- Check NetBird [admin UI](https://app.netbird.io/).
- Add more machines.
### Quickstart with self-hosted NetBird
> This is the quickest way to try self-hosted NetBird. It should take around 5 minutes to get started if you already have a public domain and a VM.
Follow the [Advanced guide with a custom identity provider](https://docs.netbird.io/selfhosted/selfhosted-guide#advanced-guide-with-a-custom-identity-provider) for installations with different IDPs.
**Infrastructure requirements:**
- A Linux VM with at least **1CPU** and **2GB** of memory.
- The VM should be publicly accessible on TCP ports **80** and **443** and UDP ports: **3478**, **49152-65535**.
- **Public domain** name pointing to the VM.
**Software requirements:**
- Docker installed on the VM with the docker compose plugin ([Docker installation guide](https://docs.docker.com/engine/install/)) or docker with docker-compose in version 2 or higher.
- [jq](https://jqlang.github.io/jq/) installed. In most distributions
Usually available in the official repositories and can be installed with `sudo apt install jq` or `sudo yum install jq`
- [curl](https://curl.se/) installed.
Usually available in the official repositories and can be installed with `sudo apt install curl` or `sudo yum install curl`
**Steps**
- Download and run the installation script:
```bash
export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started-with-zitadel.sh | bash
```
- Once finished, you can manage the resources via `docker-compose`
### A bit on NetBird internals ### A bit on NetBird internals
- Every machine in the network runs [NetBird Agent (or Client)](client/) that manages WireGuard. - Every machine in the network runs [NetBird Agent (or Client)](client/) that manages WireGuard.
- Every agent connects to [Management Service](management/) that holds network state, manages peer IPs, and distributes network updates to agents (peers). - Every agent connects to [Management Service](management/) that holds network state, manages peer IPs, and distributes network updates to agents (peers).
@@ -103,18 +89,18 @@ export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbird
[Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in NetBird setups. [Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in NetBird setups.
<p float="left" align="middle"> <p float="left" align="middle">
<img src="https://docs.netbird.io/docs-static/img/architecture/high-level-dia.png" width="700"/> <img src="https://netbird.io/docs/img/architecture/high-level-dia.png" width="700"/>
</p> </p>
See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details. See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details.
### Roadmap
- [Public Roadmap](https://github.com/netbirdio/netbird/projects/2)
### Community projects ### Community projects
- [NetBird on OpenWRT](https://github.com/messense/openwrt-netbird) - [NetBird on OpenWRT](https://github.com/messense/openwrt-netbird)
- [NetBird installer script](https://github.com/physk/netbird-installer) - [NetBird installer script](https://github.com/physk/netbird-installer)
**Note**: The `main` branch may be in an *unstable or even broken state* during development.
For stable versions, see [releases](https://github.com/netbirdio/netbird/releases).
### Support acknowledgement ### Support acknowledgement
In November 2022, NetBird joined the [StartUpSecure program](https://www.forschung-it-sicherheit-kommunikationssysteme.de/foerderung/bekanntmachungen/startup-secure) sponsored by The Federal Ministry of Education and Research of The Federal Republic of Germany. Together with [CISPA Helmholtz Center for Information Security](https://cispa.de/en) NetBird brings the security best practices and simplicity to private networking. In November 2022, NetBird joined the [StartUpSecure program](https://www.forschung-it-sicherheit-kommunikationssysteme.de/foerderung/bekanntmachungen/startup-secure) sponsored by The Federal Ministry of Education and Research of The Federal Republic of Germany. Together with [CISPA Helmholtz Center for Information Security](https://cispa.de/en) NetBird brings the security best practices and simplicity to private networking.
@@ -122,7 +108,7 @@ In November 2022, NetBird joined the [StartUpSecure program](https://www.forschu
![CISPA_Logo_BLACK_EN_RZ_RGB (1)](https://user-images.githubusercontent.com/700848/203091324-c6d311a0-22b5-4b05-a288-91cbc6cdcc46.png) ![CISPA_Logo_BLACK_EN_RZ_RGB (1)](https://user-images.githubusercontent.com/700848/203091324-c6d311a0-22b5-4b05-a288-91cbc6cdcc46.png)
### Testimonials ### Testimonials
We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [Coturn](https://github.com/coturn/coturn), and [Rosenpass](https://rosenpass.eu). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g. giving a star or a contribution). We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), and [Coturn](https://github.com/coturn/coturn). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g. giving a star or a contribution).
### Legal ### Legal
_WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld. _WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld.

View File

@@ -18,9 +18,10 @@ func Encode(num uint32) string {
} }
var encoded strings.Builder var encoded strings.Builder
remainder := uint32(0)
for num > 0 { for num > 0 {
remainder := num % base remainder = num % base
encoded.WriteByte(alphabet[remainder]) encoded.WriteByte(alphabet[remainder])
num /= base num /= base
} }

View File

@@ -1,5 +1,7 @@
FROM alpine:3 FROM gcr.io/distroless/base:debug
RUN apk add --no-cache ca-certificates iptables ip6tables
ENV NB_FOREGROUND_MODE=true ENV NB_FOREGROUND_MODE=true
ENV PATH=/sbin:/usr/sbin:/bin:/usr/bin:/busybox
SHELL ["/busybox/sh","-c"]
RUN sed -i -E 's/(^root:.+)\/sbin\/nologin/\1\/busybox\/sh/g' /etc/passwd
ENTRYPOINT [ "/go/bin/netbird","up"] ENTRYPOINT [ "/go/bin/netbird","up"]
COPY netbird /go/bin/netbird COPY netbird /go/bin/netbird

View File

@@ -55,6 +55,7 @@ type Client struct {
ctxCancelLock *sync.Mutex ctxCancelLock *sync.Mutex
deviceName string deviceName string
routeListener routemanager.RouteListener routeListener routemanager.RouteListener
onHostDnsFn func([]string)
} }
// NewClient instantiate a new Client // NewClient instantiate a new Client
@@ -96,30 +97,7 @@ func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsRead
// todo do not throw error in case of cancelled context // todo do not throw error in case of cancelled context
ctx = internal.CtxInitState(ctx) ctx = internal.CtxInitState(ctx)
return internal.RunClientMobile(ctx, cfg, c.recorder, c.tunAdapter, c.iFaceDiscover, c.routeListener, dns.items, dnsReadyListener) c.onHostDnsFn = func([]string) {}
}
// RunWithoutLogin we apply this type of run function when the backed has been started without UI (i.e. after reboot).
// In this case make no sense handle registration steps.
func (c *Client) RunWithoutLogin(dns *DNSList, dnsReadyListener DnsReadyListener) error {
cfg, err := internal.UpdateOrCreateConfig(internal.ConfigInput{
ConfigPath: c.cfgFile,
})
if err != nil {
return err
}
c.recorder.UpdateManagementAddress(cfg.ManagementURL.String())
var ctx context.Context
//nolint
ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName)
c.ctxCancelLock.Lock()
ctx, c.ctxCancel = context.WithCancel(ctxWithValues)
defer c.ctxCancel()
c.ctxCancelLock.Unlock()
// todo do not throw error in case of cancelled context
ctx = internal.CtxInitState(ctx)
return internal.RunClientMobile(ctx, cfg, c.recorder, c.tunAdapter, c.iFaceDiscover, c.routeListener, dns.items, dnsReadyListener) return internal.RunClientMobile(ctx, cfg, c.recorder, c.tunAdapter, c.iFaceDiscover, c.routeListener, dns.items, dnsReadyListener)
} }

View File

@@ -84,14 +84,10 @@ func (a *Auth) SaveConfigIfSSOSupported(listener SSOListener) {
func (a *Auth) saveConfigIfSSOSupported() (bool, error) { func (a *Auth) saveConfigIfSSOSupported() (bool, error) {
supportsSSO := true supportsSSO := true
err := a.withBackOff(a.ctx, func() (err error) { err := a.withBackOff(a.ctx, func() (err error) {
_, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) { if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound {
_, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
s, ok := gstatus.FromError(err) if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound {
if !ok {
return err
}
if s.Code() == codes.NotFound || s.Code() == codes.Unimplemented {
supportsSSO = false supportsSSO = false
err = nil err = nil
} }

View File

@@ -3,19 +3,21 @@ package cmd
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/netbirdio/netbird/client/internal/auth"
"strings" "strings"
"time" "time"
"github.com/skratchdot/open-golang/open" "github.com/skratchdot/open-golang/open"
"github.com/spf13/cobra"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status" gstatus "google.golang.org/grpc/status"
"github.com/netbirdio/netbird/util"
"github.com/spf13/cobra"
"github.com/netbirdio/netbird/client/internal" "github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/internal/auth"
"github.com/netbirdio/netbird/client/proto" "github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/client/system" "github.com/netbirdio/netbird/client/system"
"github.com/netbirdio/netbird/util"
) )
var loginCmd = &cobra.Command{ var loginCmd = &cobra.Command{
@@ -189,16 +191,17 @@ func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *int
func openURL(cmd *cobra.Command, verificationURIComplete, userCode string) { func openURL(cmd *cobra.Command, verificationURIComplete, userCode string) {
var codeMsg string var codeMsg string
if userCode != "" && !strings.Contains(verificationURIComplete, userCode) { if userCode != "" {
codeMsg = fmt.Sprintf("and enter the code %s to authenticate.", userCode) if !strings.Contains(verificationURIComplete, userCode) {
codeMsg = fmt.Sprintf("and enter the code %s to authenticate.", userCode)
}
} }
cmd.Println("Please do the SSO login in your browser. \n" + err := open.Run(verificationURIComplete)
cmd.Printf("Please do the SSO login in your browser. \n" +
"If your browser didn't open automatically, use this URL to log in:\n\n" + "If your browser didn't open automatically, use this URL to log in:\n\n" +
verificationURIComplete + " " + codeMsg) " " + verificationURIComplete + " " + codeMsg + " \n\n")
cmd.Println("") if err != nil {
if err := open.Run(verificationURIComplete); err != nil { cmd.Printf("Alternatively, you may want to use a setup key, see:\n\n https://www.netbird.io/docs/overview/setup-keys\n")
cmd.Println("\nAlternatively, you may want to use a setup key, see:\n\n" +
"https://docs.netbird.io/how-to/register-machines-using-setup-keys")
} }
} }

View File

@@ -109,9 +109,9 @@ func statusFunc(cmd *cobra.Command, args []string) error {
ctx := internal.CtxInitState(context.Background()) ctx := internal.CtxInitState(context.Background())
resp, err := getStatus(ctx, cmd) resp, _ := getStatus(ctx, cmd)
if err != nil { if err != nil {
return err return nil
} }
if resp.GetStatus() == string(internal.StatusNeedsLogin) || resp.GetStatus() == string(internal.StatusLoginFailed) { if resp.GetStatus() == string(internal.StatusNeedsLogin) || resp.GetStatus() == string(internal.StatusLoginFailed) {
@@ -120,7 +120,7 @@ func statusFunc(cmd *cobra.Command, args []string) error {
" netbird up \n\n"+ " netbird up \n\n"+
"If you are running a self-hosted version and no SSO provider has been configured in your Management Server,\n"+ "If you are running a self-hosted version and no SSO provider has been configured in your Management Server,\n"+
"you can use a setup-key:\n\n netbird up --management-url <YOUR_MANAGEMENT_URL> --setup-key <YOUR_SETUP_KEY>\n\n"+ "you can use a setup-key:\n\n netbird up --management-url <YOUR_MANAGEMENT_URL> --setup-key <YOUR_SETUP_KEY>\n\n"+
"More info: https://docs.netbird.io/how-to/register-machines-using-setup-keys\n\n", "More info: https://www.netbird.io/docs/overview/setup-keys\n\n",
resp.GetStatus(), resp.GetStatus(),
) )
return nil return nil
@@ -133,7 +133,7 @@ func statusFunc(cmd *cobra.Command, args []string) error {
outputInformationHolder := convertToStatusOutputOverview(resp) outputInformationHolder := convertToStatusOutputOverview(resp)
var statusOutputString string statusOutputString := ""
switch { switch {
case detailFlag: case detailFlag:
statusOutputString = parseToFullDetailSummary(outputInformationHolder) statusOutputString = parseToFullDetailSummary(outputInformationHolder)

View File

@@ -76,12 +76,12 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
return nil, nil return nil, nil
} }
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
eventStore, false) eventStore)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig) turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager, nil, nil) mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -40,9 +40,6 @@ const (
// It declares methods which handle actions required by the // It declares methods which handle actions required by the
// Netbird client for ACL and routing functionality // Netbird client for ACL and routing functionality
type Manager interface { type Manager interface {
// AllowNetbird allows netbird interface traffic
AllowNetbird() error
// AddFiltering rule to the firewall // AddFiltering rule to the firewall
// //
// If comment argument is empty firewall manager should set // If comment argument is empty firewall manager should set

View File

@@ -44,7 +44,6 @@ type Manager struct {
type iFaceMapper interface { type iFaceMapper interface {
Name() string Name() string
Address() iface.WGAddress Address() iface.WGAddress
IsUserspaceBind() bool
} }
type ruleset struct { type ruleset struct {
@@ -53,7 +52,7 @@ type ruleset struct {
} }
// Create iptables firewall manager // Create iptables firewall manager
func Create(wgIface iFaceMapper, ipv6Supported bool) (*Manager, error) { func Create(wgIface iFaceMapper) (*Manager, error) {
m := &Manager{ m := &Manager{
wgIface: wgIface, wgIface: wgIface,
inputDefaultRuleSpecs: []string{ inputDefaultRuleSpecs: []string{
@@ -63,26 +62,26 @@ func Create(wgIface iFaceMapper, ipv6Supported bool) (*Manager, error) {
rulesets: make(map[string]ruleset), rulesets: make(map[string]ruleset),
} }
err := ipset.Init() if err := ipset.Init(); err != nil {
if err != nil {
return nil, fmt.Errorf("init ipset: %w", err) return nil, fmt.Errorf("init ipset: %w", err)
} }
// init clients for booth ipv4 and ipv6 // init clients for booth ipv4 and ipv6
m.ipv4Client, err = iptables.NewWithProtocol(iptables.ProtocolIPv4) ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
if err != nil { if err != nil {
return nil, fmt.Errorf("iptables is not installed in the system or not supported") return nil, fmt.Errorf("iptables is not installed in the system or not supported")
} }
if isIptablesClientAvailable(ipv4Client) {
if ipv6Supported { m.ipv4Client = ipv4Client
m.ipv6Client, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
if err != nil {
log.Warnf("ip6tables is not installed in the system or not supported: %v. Access rules for this protocol won't be applied.", err)
}
} }
if m.ipv4Client == nil && m.ipv6Client == nil { ipv6Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
return nil, fmt.Errorf("iptables is not installed in the system or not enough permissions to use it") if err != nil {
log.Errorf("ip6tables is not installed in the system or not supported: %v", err)
} else {
if isIptablesClientAvailable(ipv6Client) {
m.ipv6Client = ipv6Client
}
} }
if err := m.Reset(); err != nil { if err := m.Reset(); err != nil {
@@ -91,6 +90,11 @@ func Create(wgIface iFaceMapper, ipv6Supported bool) (*Manager, error) {
return m, nil return m, nil
} }
func isIptablesClientAvailable(client *iptables.IPTables) bool {
_, err := client.ListChains("filter")
return err == nil
}
// AddFiltering rule to the firewall // AddFiltering rule to the firewall
// //
// If comment is empty rule ID is used as comment // If comment is empty rule ID is used as comment
@@ -272,38 +276,6 @@ func (m *Manager) Reset() error {
return nil return nil
} }
// AllowNetbird allows netbird interface traffic
func (m *Manager) AllowNetbird() error {
if m.wgIface.IsUserspaceBind() {
_, err := m.AddFiltering(
net.ParseIP("0.0.0.0"),
"all",
nil,
nil,
fw.RuleDirectionIN,
fw.ActionAccept,
"",
"allow netbird interface traffic",
)
if err != nil {
return fmt.Errorf("failed to allow netbird interface traffic: %w", err)
}
_, err = m.AddFiltering(
net.ParseIP("0.0.0.0"),
"all",
nil,
nil,
fw.RuleDirectionOUT,
fw.ActionAccept,
"",
"allow netbird interface traffic",
)
return err
}
return nil
}
// Flush doesn't need to be implemented for this manager // Flush doesn't need to be implemented for this manager
func (m *Manager) Flush() error { return nil } func (m *Manager) Flush() error { return nil }
@@ -434,7 +406,7 @@ func (m *Manager) client(ip net.IP) (*iptables.IPTables, error) {
return nil, fmt.Errorf("failed to create default drop all in netbird input chain: %w", err) return nil, fmt.Errorf("failed to create default drop all in netbird input chain: %w", err)
} }
if err := client.Insert("filter", "INPUT", 1, m.inputDefaultRuleSpecs...); err != nil { if err := client.AppendUnique("filter", "INPUT", m.inputDefaultRuleSpecs...); err != nil {
return nil, fmt.Errorf("failed to create input chain jump rule: %w", err) return nil, fmt.Errorf("failed to create input chain jump rule: %w", err)
} }

View File

@@ -33,8 +33,6 @@ func (i *iFaceMock) Address() iface.WGAddress {
panic("AddressFunc is not set") panic("AddressFunc is not set")
} }
func (i *iFaceMock) IsUserspaceBind() bool { return false }
func TestIptablesManager(t *testing.T) { func TestIptablesManager(t *testing.T) {
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
require.NoError(t, err) require.NoError(t, err)
@@ -55,7 +53,7 @@ func TestIptablesManager(t *testing.T) {
} }
// just check on the local interface // just check on the local interface
manager, err := Create(mock, true) manager, err := Create(mock)
require.NoError(t, err) require.NoError(t, err)
time.Sleep(time.Second) time.Sleep(time.Second)
@@ -143,7 +141,7 @@ func TestIptablesManagerIPSet(t *testing.T) {
} }
// just check on the local interface // just check on the local interface
manager, err := Create(mock, true) manager, err := Create(mock)
require.NoError(t, err) require.NoError(t, err)
time.Sleep(time.Second) time.Sleep(time.Second)
@@ -231,7 +229,7 @@ func TestIptablesCreatePerformance(t *testing.T) {
for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} { for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} {
t.Run(fmt.Sprintf("Testing %d rules", testMax), func(t *testing.T) { t.Run(fmt.Sprintf("Testing %d rules", testMax), func(t *testing.T) {
// just check on the local interface // just check on the local interface
manager, err := Create(mock, true) manager, err := Create(mock)
require.NoError(t, err) require.NoError(t, err)
time.Sleep(time.Second) time.Sleep(time.Second)

View File

@@ -29,8 +29,6 @@ const (
// FilterOutputChainName is the name of the chain that is used for filtering outgoing packets // FilterOutputChainName is the name of the chain that is used for filtering outgoing packets
FilterOutputChainName = "netbird-acl-output-filter" FilterOutputChainName = "netbird-acl-output-filter"
AllowNetbirdInputRuleID = "allow Netbird incoming traffic"
) )
var anyIP = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} var anyIP = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
@@ -381,7 +379,7 @@ func (m *Manager) chain(
if c != nil { if c != nil {
return c, nil return c, nil
} }
return m.createChainIfNotExists(tf, FilterTableName, name, hook, priority, cType) return m.createChainIfNotExists(tf, name, hook, priority, cType)
} }
if ip.To4() != nil { if ip.To4() != nil {
@@ -401,20 +399,13 @@ func (m *Manager) chain(
} }
// table returns the table for the given family of the IP address // table returns the table for the given family of the IP address
func (m *Manager) table( func (m *Manager) table(family nftables.TableFamily) (*nftables.Table, error) {
family nftables.TableFamily, tableName string,
) (*nftables.Table, error) {
// we cache access to Netbird ACL table only
if tableName != FilterTableName {
return m.createTableIfNotExists(nftables.TableFamilyIPv4, tableName)
}
if family == nftables.TableFamilyIPv4 { if family == nftables.TableFamilyIPv4 {
if m.tableIPv4 != nil { if m.tableIPv4 != nil {
return m.tableIPv4, nil return m.tableIPv4, nil
} }
table, err := m.createTableIfNotExists(nftables.TableFamilyIPv4, tableName) table, err := m.createTableIfNotExists(nftables.TableFamilyIPv4)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -426,7 +417,7 @@ func (m *Manager) table(
return m.tableIPv6, nil return m.tableIPv6, nil
} }
table, err := m.createTableIfNotExists(nftables.TableFamilyIPv6, tableName) table, err := m.createTableIfNotExists(nftables.TableFamilyIPv6)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -434,21 +425,19 @@ func (m *Manager) table(
return m.tableIPv6, nil return m.tableIPv6, nil
} }
func (m *Manager) createTableIfNotExists( func (m *Manager) createTableIfNotExists(family nftables.TableFamily) (*nftables.Table, error) {
family nftables.TableFamily, tableName string,
) (*nftables.Table, error) {
tables, err := m.rConn.ListTablesOfFamily(family) tables, err := m.rConn.ListTablesOfFamily(family)
if err != nil { if err != nil {
return nil, fmt.Errorf("list of tables: %w", err) return nil, fmt.Errorf("list of tables: %w", err)
} }
for _, t := range tables { for _, t := range tables {
if t.Name == tableName { if t.Name == FilterTableName {
return t, nil return t, nil
} }
} }
table := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4}) table := m.rConn.AddTable(&nftables.Table{Name: FilterTableName, Family: nftables.TableFamilyIPv4})
if err := m.rConn.Flush(); err != nil { if err := m.rConn.Flush(); err != nil {
return nil, err return nil, err
} }
@@ -457,13 +446,12 @@ func (m *Manager) createTableIfNotExists(
func (m *Manager) createChainIfNotExists( func (m *Manager) createChainIfNotExists(
family nftables.TableFamily, family nftables.TableFamily,
tableName string,
name string, name string,
hooknum nftables.ChainHook, hooknum nftables.ChainHook,
priority nftables.ChainPriority, priority nftables.ChainPriority,
chainType nftables.ChainType, chainType nftables.ChainType,
) (*nftables.Chain, error) { ) (*nftables.Chain, error) {
table, err := m.table(family, tableName) table, err := m.table(family)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -650,22 +638,6 @@ func (m *Manager) Reset() error {
return fmt.Errorf("list of chains: %w", err) return fmt.Errorf("list of chains: %w", err)
} }
for _, c := range chains { for _, c := range chains {
// delete Netbird allow input traffic rule if it exists
if c.Table.Name == "filter" && c.Name == "INPUT" {
rules, err := m.rConn.GetRules(c.Table, c)
if err != nil {
log.Errorf("get rules for chain %q: %v", c.Name, err)
continue
}
for _, r := range rules {
if bytes.Equal(r.UserData, []byte(AllowNetbirdInputRuleID)) {
if err := m.rConn.DelRule(r); err != nil {
log.Errorf("delete rule: %v", err)
}
}
}
}
if c.Name == FilterInputChainName || c.Name == FilterOutputChainName { if c.Name == FilterInputChainName || c.Name == FilterOutputChainName {
m.rConn.DelChain(c) m.rConn.DelChain(c)
} }
@@ -730,53 +702,6 @@ func (m *Manager) Flush() error {
return nil return nil
} }
// AllowNetbird allows netbird interface traffic
func (m *Manager) AllowNetbird() error {
m.mutex.Lock()
defer m.mutex.Unlock()
tf := nftables.TableFamilyIPv4
if m.wgIface.Address().IP.To4() == nil {
tf = nftables.TableFamilyIPv6
}
chains, err := m.rConn.ListChainsOfTableFamily(tf)
if err != nil {
return fmt.Errorf("list of chains: %w", err)
}
var chain *nftables.Chain
for _, c := range chains {
if c.Table.Name == "filter" && c.Name == "INPUT" {
chain = c
break
}
}
if chain == nil {
log.Debugf("chain INPUT not found. Skiping add allow netbird rule")
return nil
}
rules, err := m.rConn.GetRules(chain.Table, chain)
if err != nil {
return fmt.Errorf("failed to get rules for the INPUT chain: %v", err)
}
if rule := m.detectAllowNetbirdRule(rules); rule != nil {
log.Debugf("allow netbird rule already exists: %v", rule)
return nil
}
m.applyAllowNetbirdRules(chain)
err = m.rConn.Flush()
if err != nil {
return fmt.Errorf("failed to flush allow input netbird rules: %v", err)
}
return nil
}
func (m *Manager) flushWithBackoff() (err error) { func (m *Manager) flushWithBackoff() (err error) {
backoff := 4 backoff := 4
backoffTime := 1000 * time.Millisecond backoffTime := 1000 * time.Millisecond
@@ -820,44 +745,6 @@ func (m *Manager) refreshRuleHandles(table *nftables.Table, chain *nftables.Chai
return nil return nil
} }
func (m *Manager) applyAllowNetbirdRules(chain *nftables.Chain) {
rule := &nftables.Rule{
Table: chain.Table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: ifname(m.wgIface.Name()),
},
&expr.Verdict{
Kind: expr.VerdictAccept,
},
},
UserData: []byte(AllowNetbirdInputRuleID),
}
_ = m.rConn.InsertRule(rule)
}
func (m *Manager) detectAllowNetbirdRule(existedRules []*nftables.Rule) *nftables.Rule {
ifName := ifname(m.wgIface.Name())
for _, rule := range existedRules {
if rule.Table.Name == "filter" && rule.Chain.Name == "INPUT" {
if len(rule.Exprs) < 4 {
if e, ok := rule.Exprs[0].(*expr.Meta); !ok || e.Key != expr.MetaKeyIIFNAME {
continue
}
if e, ok := rule.Exprs[1].(*expr.Cmp); !ok || e.Op != expr.CmpOpEq || !bytes.Equal(e.Data, ifName) {
continue
}
return rule
}
}
}
return nil
}
func encodePort(port fw.Port) []byte { func encodePort(port fw.Port) []byte {
bs := make([]byte, 2) bs := make([]byte, 2)
binary.BigEndian.PutUint16(bs, uint16(port.Values[0])) binary.BigEndian.PutUint16(bs, uint16(port.Values[0]))

View File

@@ -1,19 +0,0 @@
//go:build !windows && !linux
package uspfilter
// Reset firewall to the default state
func (m *Manager) Reset() error {
m.mutex.Lock()
defer m.mutex.Unlock()
m.outgoingRules = make(map[string]RuleSet)
m.incomingRules = make(map[string]RuleSet)
return nil
}
// AllowNetbird allows netbird interface traffic
func (m *Manager) AllowNetbird() error {
return nil
}

View File

@@ -1,21 +0,0 @@
package uspfilter
// AllowNetbird allows netbird interface traffic
func (m *Manager) AllowNetbird() error {
return nil
}
// Reset firewall to the default state
func (m *Manager) Reset() error {
m.mutex.Lock()
defer m.mutex.Unlock()
m.outgoingRules = make(map[string]RuleSet)
m.incomingRules = make(map[string]RuleSet)
if m.resetHook != nil {
return m.resetHook()
}
return nil
}

View File

@@ -1,91 +0,0 @@
package uspfilter
import (
"errors"
"fmt"
"os/exec"
"strings"
"syscall"
)
type action string
const (
addRule action = "add"
deleteRule action = "delete"
firewallRuleName = "Netbird"
noRulesMatchCriteria = "No rules match the specified criteria"
)
// Reset firewall to the default state
func (m *Manager) Reset() error {
m.mutex.Lock()
defer m.mutex.Unlock()
m.outgoingRules = make(map[string]RuleSet)
m.incomingRules = make(map[string]RuleSet)
if err := manageFirewallRule(firewallRuleName, deleteRule); err != nil {
return fmt.Errorf("couldn't remove windows firewall: %w", err)
}
return nil
}
// AllowNetbird allows netbird interface traffic
func (m *Manager) AllowNetbird() error {
return manageFirewallRule(firewallRuleName,
addRule,
"dir=in",
"enable=yes",
"action=allow",
"profile=any",
"localip="+m.wgIface.Address().IP.String(),
)
}
func manageFirewallRule(ruleName string, action action, args ...string) error {
active, err := isFirewallRuleActive(ruleName)
if err != nil {
return err
}
if (action == addRule && !active) || (action == deleteRule && active) {
baseArgs := []string{"advfirewall", "firewall", string(action), "rule", "name=" + ruleName}
args := append(baseArgs, args...)
cmd := exec.Command("netsh", args...)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
return cmd.Run()
}
return nil
}
func isFirewallRuleActive(ruleName string) (bool, error) {
args := []string{"advfirewall", "firewall", "show", "rule", "name=" + ruleName}
cmd := exec.Command("netsh", args...)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
output, err := cmd.Output()
if err != nil {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
// if the firewall rule is not active, we expect last exit code to be 1
exitStatus := exitError.Sys().(syscall.WaitStatus).ExitStatus()
if exitStatus == 1 {
if strings.Contains(string(output), noRulesMatchCriteria) {
return false, nil
}
}
}
return false, err
}
if strings.Contains(string(output), noRulesMatchCriteria) {
return false, nil
}
return true, nil
}

View File

@@ -19,7 +19,6 @@ const layerTypeAll = 0
// IFaceMapper defines subset methods of interface required for manager // IFaceMapper defines subset methods of interface required for manager
type IFaceMapper interface { type IFaceMapper interface {
SetFilter(iface.PacketFilter) error SetFilter(iface.PacketFilter) error
Address() iface.WGAddress
} }
// RuleSet is a set of rules grouped by a string key // RuleSet is a set of rules grouped by a string key
@@ -31,8 +30,6 @@ type Manager struct {
incomingRules map[string]RuleSet incomingRules map[string]RuleSet
wgNetwork *net.IPNet wgNetwork *net.IPNet
decoders sync.Pool decoders sync.Pool
wgIface IFaceMapper
resetHook func() error
mutex sync.RWMutex mutex sync.RWMutex
} }
@@ -68,7 +65,6 @@ func Create(iface IFaceMapper) (*Manager, error) {
}, },
outgoingRules: make(map[string]RuleSet), outgoingRules: make(map[string]RuleSet),
incomingRules: make(map[string]RuleSet), incomingRules: make(map[string]RuleSet),
wgIface: iface,
} }
if err := iface.SetFilter(m); err != nil { if err := iface.SetFilter(m); err != nil {
@@ -175,6 +171,17 @@ func (m *Manager) DeleteRule(rule fw.Rule) error {
return nil return nil
} }
// Reset firewall to the default state
func (m *Manager) Reset() error {
m.mutex.Lock()
defer m.mutex.Unlock()
m.outgoingRules = make(map[string]RuleSet)
m.incomingRules = make(map[string]RuleSet)
return nil
}
// Flush doesn't need to be implemented for this manager // Flush doesn't need to be implemented for this manager
func (m *Manager) Flush() error { return nil } func (m *Manager) Flush() error { return nil }
@@ -368,8 +375,3 @@ func (m *Manager) RemovePacketHook(hookID string) error {
} }
return fmt.Errorf("hook with given id not found") return fmt.Errorf("hook with given id not found")
} }
// SetResetHook which will be executed in the end of Reset method
func (m *Manager) SetResetHook(hook func() error) {
m.resetHook = hook
}

View File

@@ -16,7 +16,6 @@ import (
type IFaceMock struct { type IFaceMock struct {
SetFilterFunc func(iface.PacketFilter) error SetFilterFunc func(iface.PacketFilter) error
AddressFunc func() iface.WGAddress
} }
func (i *IFaceMock) SetFilter(iface iface.PacketFilter) error { func (i *IFaceMock) SetFilter(iface iface.PacketFilter) error {
@@ -26,13 +25,6 @@ func (i *IFaceMock) SetFilter(iface iface.PacketFilter) error {
return i.SetFilterFunc(iface) return i.SetFilterFunc(iface)
} }
func (i *IFaceMock) Address() iface.WGAddress {
if i.AddressFunc == nil {
return iface.WGAddress{}
}
return i.AddressFunc()
}
func TestManagerCreate(t *testing.T) { func TestManagerCreate(t *testing.T) {
ifaceMock := &IFaceMock{ ifaceMock := &IFaceMock{
SetFilterFunc: func(iface.PacketFilter) error { return nil }, SetFilterFunc: func(iface.PacketFilter) error { return nil },

View File

@@ -146,11 +146,12 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) {
// if this rule is member of rule selection with more than DefaultIPsCountForSet // if this rule is member of rule selection with more than DefaultIPsCountForSet
// it's IP address can be used in the ipset for firewall manager which supports it // it's IP address can be used in the ipset for firewall manager which supports it
ipset := ipsetByRuleSelectors[d.getRuleGroupingSelector(r)] ipset := ipsetByRuleSelectors[d.getRuleGroupingSelector(r)]
ipsetName := ""
if ipset.name == "" { if ipset.name == "" {
d.ipsetCounter++ d.ipsetCounter++
ipset.name = fmt.Sprintf("nb%07d", d.ipsetCounter) ipset.name = fmt.Sprintf("nb%07d", d.ipsetCounter)
} }
ipsetName := ipset.name ipsetName = ipset.name
pairID, rulePair, err := d.protoRuleToFirewallRule(r, ipsetName) pairID, rulePair, err := d.protoRuleToFirewallRule(r, ipsetName)
if err != nil { if err != nil {
log.Errorf("failed to apply firewall rule: %+v, %v", r, err) log.Errorf("failed to apply firewall rule: %+v, %v", r, err)

View File

@@ -1,4 +1,4 @@
//go:build !linux || android //go:build !linux
package acl package acl
@@ -6,8 +6,6 @@ import (
"fmt" "fmt"
"runtime" "runtime"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/firewall/uspfilter" "github.com/netbirdio/netbird/client/firewall/uspfilter"
) )
@@ -19,9 +17,6 @@ func Create(iface IFaceMapper) (manager *DefaultManager, err error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := fm.AllowNetbird(); err != nil {
log.Errorf("failed to allow netbird interface traffic: %v", err)
}
return newDefaultManager(fm), nil return newDefaultManager(fm), nil
} }
return nil, fmt.Errorf("not implemented for this OS: %s", runtime.GOOS) return nil, fmt.Errorf("not implemented for this OS: %s", runtime.GOOS)

View File

@@ -1,5 +1,3 @@
//go:build !android
package acl package acl
import ( import (
@@ -9,68 +7,26 @@ import (
"github.com/netbirdio/netbird/client/firewall/iptables" "github.com/netbirdio/netbird/client/firewall/iptables"
"github.com/netbirdio/netbird/client/firewall/nftables" "github.com/netbirdio/netbird/client/firewall/nftables"
"github.com/netbirdio/netbird/client/firewall/uspfilter" "github.com/netbirdio/netbird/client/firewall/uspfilter"
"github.com/netbirdio/netbird/client/internal/checkfw"
) )
// Create creates a firewall manager instance for the Linux // Create creates a firewall manager instance for the Linux
func Create(iface IFaceMapper) (*DefaultManager, error) { func Create(iface IFaceMapper) (manager *DefaultManager, err error) {
// on the linux system we try to user nftables or iptables
// in any case, because we need to allow netbird interface traffic
// so we use AllowNetbird traffic from these firewall managers
// for the userspace packet filtering firewall
var fm firewall.Manager var fm firewall.Manager
var err error
checkResult := checkfw.Check()
switch checkResult {
case checkfw.IPTABLES, checkfw.IPTABLESWITHV6:
log.Debug("creating an iptables firewall manager for access control")
ipv6Supported := checkResult == checkfw.IPTABLESWITHV6
if fm, err = iptables.Create(iface, ipv6Supported); err != nil {
log.Infof("failed to create iptables manager for access control: %s", err)
}
case checkfw.NFTABLES:
log.Debug("creating an nftables firewall manager for access control")
if fm, err = nftables.Create(iface); err != nil {
log.Debugf("failed to create nftables manager for access control: %s", err)
}
}
var resetHookForUserspace func() error
if fm != nil && err == nil {
// err shadowing is used here, to ignore this error
if err := fm.AllowNetbird(); err != nil {
log.Errorf("failed to allow netbird interface traffic: %v", err)
}
resetHookForUserspace = fm.Reset
}
if iface.IsUserspaceBind() { if iface.IsUserspaceBind() {
// use userspace packet filtering firewall // use userspace packet filtering firewall
usfm, err := uspfilter.Create(iface) if fm, err = uspfilter.Create(iface); err != nil {
if err != nil {
log.Debugf("failed to create userspace filtering firewall: %s", err) log.Debugf("failed to create userspace filtering firewall: %s", err)
return nil, err return nil, err
} }
} else {
// set kernel space firewall Reset as hook for userspace firewall if fm, err = nftables.Create(iface); err != nil {
// manager Reset method, to clean up log.Debugf("failed to create nftables manager: %s", err)
if resetHookForUserspace != nil { // fallback to iptables
usfm.SetResetHook(resetHookForUserspace) if fm, err = iptables.Create(iface); err != nil {
log.Errorf("failed to create iptables manager: %s", err)
return nil, err
}
} }
// to be consistent for any future extensions.
// ignore this error
if err := usfm.AllowNetbird(); err != nil {
log.Errorf("failed to allow netbird interface traffic: %v", err)
}
fm = usfm
}
if fm == nil || err != nil {
log.Errorf("failed to create firewall manager: %s", err)
// no firewall manager found or initialized correctly
return nil, err
} }
return newDefaultManager(fm), nil return newDefaultManager(fm), nil

View File

@@ -1,13 +1,11 @@
package acl package acl
import ( import (
"net"
"testing" "testing"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/netbirdio/netbird/client/internal/acl/mocks" "github.com/netbirdio/netbird/client/internal/acl/mocks"
"github.com/netbirdio/netbird/iface"
mgmProto "github.com/netbirdio/netbird/management/proto" mgmProto "github.com/netbirdio/netbird/management/proto"
) )
@@ -34,22 +32,13 @@ func TestDefaultManager(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
ifaceMock := mocks.NewMockIFaceMapper(ctrl) iface := mocks.NewMockIFaceMapper(ctrl)
ifaceMock.EXPECT().IsUserspaceBind().Return(true) iface.EXPECT().IsUserspaceBind().Return(true)
ifaceMock.EXPECT().SetFilter(gomock.Any()) // iface.EXPECT().Name().Return("lo")
ip, network, err := net.ParseCIDR("172.0.0.1/32") iface.EXPECT().SetFilter(gomock.Any())
if err != nil {
t.Fatalf("failed to parse IP address: %v", err)
}
ifaceMock.EXPECT().Name().Return("lo").AnyTimes()
ifaceMock.EXPECT().Address().Return(iface.WGAddress{
IP: ip,
Network: network,
}).AnyTimes()
// we receive one rule from the management so for testing purposes ignore it // we receive one rule from the management so for testing purposes ignore it
acl, err := Create(ifaceMock) acl, err := Create(iface)
if err != nil { if err != nil {
t.Errorf("create ACL manager: %v", err) t.Errorf("create ACL manager: %v", err)
return return
@@ -322,22 +311,13 @@ func TestDefaultManagerEnableSSHRules(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
ifaceMock := mocks.NewMockIFaceMapper(ctrl) iface := mocks.NewMockIFaceMapper(ctrl)
ifaceMock.EXPECT().IsUserspaceBind().Return(true) iface.EXPECT().IsUserspaceBind().Return(true)
ifaceMock.EXPECT().SetFilter(gomock.Any()) // iface.EXPECT().Name().Return("lo")
ip, network, err := net.ParseCIDR("172.0.0.1/32") iface.EXPECT().SetFilter(gomock.Any())
if err != nil {
t.Fatalf("failed to parse IP address: %v", err)
}
ifaceMock.EXPECT().Name().Return("lo").AnyTimes()
ifaceMock.EXPECT().Address().Return(iface.WGAddress{
IP: ip,
Network: network,
}).AnyTimes()
// we receive one rule from the management so for testing purposes ignore it // we receive one rule from the management so for testing purposes ignore it
acl, err := Create(ifaceMock) acl, err := Create(iface)
if err != nil { if err != nil {
t.Errorf("create ACL manager: %v", err) t.Errorf("create ACL manager: %v", err)
return return

View File

@@ -4,8 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"runtime"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status" gstatus "google.golang.org/grpc/status"
@@ -57,50 +57,34 @@ func (t TokenInfo) GetTokenToUse() string {
return t.AccessToken return t.AccessToken
} }
// NewOAuthFlow initializes and returns the appropriate OAuth flow based on the management configuration // NewOAuthFlow initializes and returns the appropriate OAuth flow based on the management configuration.
//
// It starts by initializing the PKCE.If this process fails, it resorts to the Device Code Flow,
// and if that also fails, the authentication process is deemed unsuccessful
//
// On Linux distros without desktop environment support, it only tries to initialize the Device Code Flow
func NewOAuthFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) { func NewOAuthFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) {
if runtime.GOOS == "linux" && !isLinuxRunningDesktop() { log.Debug("getting device authorization flow info")
return authenticateWithDeviceCodeFlow(ctx, config)
}
pkceFlow, err := authenticateWithPKCEFlow(ctx, config) // Try to initialize the Device Authorization Flow
if err != nil {
// fallback to device code flow
return authenticateWithDeviceCodeFlow(ctx, config)
}
return pkceFlow, nil
}
// authenticateWithPKCEFlow initializes the Proof Key for Code Exchange flow auth flow
func authenticateWithPKCEFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) {
pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
if err != nil {
return nil, fmt.Errorf("getting pkce authorization flow info failed with error: %v", err)
}
return NewPKCEAuthorizationFlow(pkceFlowInfo.ProviderConfig)
}
// authenticateWithDeviceCodeFlow initializes the Device Code auth Flow
func authenticateWithDeviceCodeFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) {
deviceFlowInfo, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL) deviceFlowInfo, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
if err == nil {
return NewDeviceAuthorizationFlow(deviceFlowInfo.ProviderConfig)
}
log.Debugf("getting device authorization flow info failed with error: %v", err)
log.Debugf("falling back to pkce authorization flow info")
// If Device Authorization Flow failed, try the PKCE Authorization Flow
pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
if err != nil { if err != nil {
s, ok := gstatus.FromError(err) s, ok := gstatus.FromError(err)
if ok && s.Code() == codes.NotFound { if ok && s.Code() == codes.NotFound {
return nil, fmt.Errorf("no SSO provider returned from management. " + return nil, fmt.Errorf("no SSO provider returned from management. " +
"Please proceed with setting up this device using setup keys " + "If you are using hosting Netbird see documentation at " +
"https://docs.netbird.io/how-to/register-machines-using-setup-keys") "https://github.com/netbirdio/netbird/tree/main/management for details")
} else if ok && s.Code() == codes.Unimplemented { } else if ok && s.Code() == codes.Unimplemented {
return nil, fmt.Errorf("the management server, %s, does not support SSO providers, "+ return nil, fmt.Errorf("the management server, %s, does not support SSO providers, "+
"please update your server or use Setup Keys to login", config.ManagementURL) "please update your server or use Setup Keys to login", config.ManagementURL)
} else { } else {
return nil, fmt.Errorf("getting device authorization flow info failed with error: %v", err) return nil, fmt.Errorf("getting pkce authorization flow info failed with error: %v", err)
} }
} }
return NewDeviceAuthorizationFlow(deviceFlowInfo.ProviderConfig) return NewPKCEAuthorizationFlow(pkceFlowInfo.ProviderConfig)
} }

View File

@@ -5,7 +5,6 @@ import (
"crypto/sha256" "crypto/sha256"
"crypto/subtle" "crypto/subtle"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"html/template" "html/template"
"net" "net"
@@ -26,8 +25,6 @@ var _ OAuthFlow = &PKCEAuthorizationFlow{}
const ( const (
queryState = "state" queryState = "state"
queryCode = "code" queryCode = "code"
queryError = "error"
queryErrorDesc = "error_description"
defaultPKCETimeoutSeconds = 300 defaultPKCETimeoutSeconds = 300
) )
@@ -79,7 +76,7 @@ func (p *PKCEAuthorizationFlow) GetClientID(_ context.Context) string {
} }
// RequestAuthInfo requests a authorization code login flow information. // RequestAuthInfo requests a authorization code login flow information.
func (p *PKCEAuthorizationFlow) RequestAuthInfo(ctx context.Context) (AuthFlowInfo, error) { func (p *PKCEAuthorizationFlow) RequestAuthInfo(_ context.Context) (AuthFlowInfo, error) {
state, err := randomBytesInHex(24) state, err := randomBytesInHex(24)
if err != nil { if err != nil {
return AuthFlowInfo{}, fmt.Errorf("could not generate random state: %v", err) return AuthFlowInfo{}, fmt.Errorf("could not generate random state: %v", err)
@@ -113,79 +110,71 @@ func (p *PKCEAuthorizationFlow) WaitToken(ctx context.Context, _ AuthFlowInfo) (
tokenChan := make(chan *oauth2.Token, 1) tokenChan := make(chan *oauth2.Token, 1)
errChan := make(chan error, 1) errChan := make(chan error, 1)
parsedURL, err := url.Parse(p.oAuthConfig.RedirectURL) go p.startServer(tokenChan, errChan)
if err != nil {
return TokenInfo{}, fmt.Errorf("failed to parse redirect URL: %v", err)
}
server := &http.Server{Addr: fmt.Sprintf(":%s", parsedURL.Port())}
defer func() {
shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
log.Errorf("failed to close the server: %v", err)
}
}()
go p.startServer(server, tokenChan, errChan)
select { select {
case <-ctx.Done(): case <-ctx.Done():
return TokenInfo{}, ctx.Err() return TokenInfo{}, ctx.Err()
case token := <-tokenChan: case token := <-tokenChan:
return p.parseOAuthToken(token) return p.handleOAuthToken(token)
case err := <-errChan: case err := <-errChan:
return TokenInfo{}, err return TokenInfo{}, err
} }
} }
func (p *PKCEAuthorizationFlow) startServer(server *http.Server, tokenChan chan<- *oauth2.Token, errChan chan<- error) { func (p *PKCEAuthorizationFlow) startServer(tokenChan chan<- *oauth2.Token, errChan chan<- error) {
mux := http.NewServeMux() parsedURL, err := url.Parse(p.oAuthConfig.RedirectURL)
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { if err != nil {
token, err := p.handleRequest(req) errChan <- fmt.Errorf("failed to parse redirect URL: %v", err)
if err != nil { return
renderPKCEFlowTmpl(w, err) }
errChan <- fmt.Errorf("PKCE authorization flow failed: %v", err) port := parsedURL.Port()
return
server := http.Server{Addr: fmt.Sprintf(":%s", port)}
defer func() {
if err := server.Shutdown(context.Background()); err != nil {
log.Errorf("error while shutting down pkce flow server: %v", err)
}
}()
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
tokenValidatorFunc := func() (*oauth2.Token, error) {
query := req.URL.Query()
state := query.Get(queryState)
// Prevent timing attacks on state
if subtle.ConstantTimeCompare([]byte(p.state), []byte(state)) == 0 {
return nil, fmt.Errorf("invalid state")
}
code := query.Get(queryCode)
if code == "" {
return nil, fmt.Errorf("missing code")
}
return p.oAuthConfig.Exchange(
req.Context(),
code,
oauth2.SetAuthURLParam("code_verifier", p.codeVerifier),
)
}
token, err := tokenValidatorFunc()
if err != nil {
errChan <- fmt.Errorf("PKCE authorization flow failed: %v", err)
renderPKCEFlowTmpl(w, err)
} }
renderPKCEFlowTmpl(w, nil)
tokenChan <- token tokenChan <- token
renderPKCEFlowTmpl(w, nil)
}) })
server.Handler = mux if err := server.ListenAndServe(); err != nil {
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errChan <- err errChan <- err
} }
} }
func (p *PKCEAuthorizationFlow) handleRequest(req *http.Request) (*oauth2.Token, error) { func (p *PKCEAuthorizationFlow) handleOAuthToken(token *oauth2.Token) (TokenInfo, error) {
query := req.URL.Query()
if authError := query.Get(queryError); authError != "" {
authErrorDesc := query.Get(queryErrorDesc)
return nil, fmt.Errorf("%s.%s", authError, authErrorDesc)
}
// Prevent timing attacks on the state
if state := query.Get(queryState); subtle.ConstantTimeCompare([]byte(p.state), []byte(state)) == 0 {
return nil, fmt.Errorf("invalid state")
}
code := query.Get(queryCode)
if code == "" {
return nil, fmt.Errorf("missing code")
}
return p.oAuthConfig.Exchange(
req.Context(),
code,
oauth2.SetAuthURLParam("code_verifier", p.codeVerifier),
)
}
func (p *PKCEAuthorizationFlow) parseOAuthToken(token *oauth2.Token) (TokenInfo, error) {
tokenInfo := TokenInfo{ tokenInfo := TokenInfo{
AccessToken: token.AccessToken, AccessToken: token.AccessToken,
RefreshToken: token.RefreshToken, RefreshToken: token.RefreshToken,

View File

@@ -7,7 +7,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"os"
"reflect" "reflect"
"strings" "strings"
) )
@@ -61,8 +60,3 @@ func isValidAccessToken(token string, audience string) error {
return fmt.Errorf("invalid JWT token audience field") return fmt.Errorf("invalid JWT token audience field")
} }
// isLinuxRunningDesktop checks if a Linux OS is running desktop environment
func isLinuxRunningDesktop() bool {
return os.Getenv("DESKTOP_SESSION") != "" || os.Getenv("XDG_CURRENT_DESKTOP") != ""
}

View File

@@ -1,3 +0,0 @@
//go:build !linux || android
package checkfw

View File

@@ -1,56 +0,0 @@
//go:build !android
package checkfw
import (
"os"
"github.com/coreos/go-iptables/iptables"
"github.com/google/nftables"
)
const (
// UNKNOWN is the default value for the firewall type for unknown firewall type
UNKNOWN FWType = iota
// IPTABLES is the value for the iptables firewall type
IPTABLES
// IPTABLESWITHV6 is the value for the iptables firewall type with ipv6
IPTABLESWITHV6
// NFTABLES is the value for the nftables firewall type
NFTABLES
)
// SKIP_NFTABLES_ENV is the environment variable to skip nftables check
const SKIP_NFTABLES_ENV = "NB_SKIP_NFTABLES_CHECK"
// FWType is the type for the firewall type
type FWType int
// Check returns the firewall type based on common lib checks. It returns UNKNOWN if no firewall is found.
func Check() FWType {
nf := nftables.Conn{}
if _, err := nf.ListChains(); err == nil && os.Getenv(SKIP_NFTABLES_ENV) != "true" {
return NFTABLES
}
ip, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
if err == nil {
if isIptablesClientAvailable(ip) {
ipSupport := IPTABLES
ipv6, ip6Err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
if ip6Err == nil {
if isIptablesClientAvailable(ipv6) {
ipSupport = IPTABLESWITHV6
}
}
return ipSupport
}
}
return UNKNOWN
}
func isIptablesClientAvailable(client *iptables.IPTables) bool {
_, err := client.ListChains("filter")
return err == nil
}

View File

@@ -23,6 +23,9 @@ func TestGetConfig(t *testing.T) {
assert.Equal(t, config.ManagementURL.String(), DefaultManagementURL) assert.Equal(t, config.ManagementURL.String(), DefaultManagementURL)
assert.Equal(t, config.AdminURL.String(), DefaultAdminURL) assert.Equal(t, config.AdminURL.String(), DefaultAdminURL)
if err != nil {
return
}
managementURL := "https://test.management.url:33071" managementURL := "https://test.management.url:33071"
adminURL := "https://app.admin.url:443" adminURL := "https://app.admin.url:443"
path := filepath.Join(t.TempDir(), "config.json") path := filepath.Join(t.TempDir(), "config.json")

View File

@@ -179,6 +179,8 @@ func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status,
log.Print("Netbird engine started, my IP is: ", peerConfig.Address) log.Print("Netbird engine started, my IP is: ", peerConfig.Address)
state.Set(StatusConnected) state.Set(StatusConnected)
statusRecorder.ClientStart()
<-engineCtx.Done() <-engineCtx.Done()
statusRecorder.ClientTeardown() statusRecorder.ClientTeardown()
@@ -199,7 +201,6 @@ func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status,
return nil return nil
} }
statusRecorder.ClientStart()
err = backoff.Retry(operation, backOff) err = backoff.Retry(operation, backOff)
if err != nil { if err != nil {
log.Debugf("exiting client retry loop due to unrecoverable error: %s", err) log.Debugf("exiting client retry loop due to unrecoverable error: %s", err)

View File

@@ -15,8 +15,7 @@ const (
fileGeneratedResolvConfSearchBeginContent = "search " fileGeneratedResolvConfSearchBeginContent = "search "
fileGeneratedResolvConfContentFormat = fileGeneratedResolvConfContentHeader + fileGeneratedResolvConfContentFormat = fileGeneratedResolvConfContentHeader +
"\n# If needed you can restore the original file by copying back %s\n\nnameserver %s\n" + "\n# If needed you can restore the original file by copying back %s\n\nnameserver %s\n" +
fileGeneratedResolvConfSearchBeginContent + "%s\n\n" + fileGeneratedResolvConfSearchBeginContent + "%s\n"
"%s\n"
) )
const ( const (
@@ -92,12 +91,7 @@ func (f *fileConfigurator) applyDNSConfig(config hostDNSConfig) error {
searchDomains += " " + dConf.domain searchDomains += " " + dConf.domain
appendedDomains++ appendedDomains++
} }
content := fmt.Sprintf(fileGeneratedResolvConfContentFormat, fileDefaultResolvConfBackupLocation, config.serverIP, searchDomains)
originalContent, err := os.ReadFile(fileDefaultResolvConfBackupLocation)
if err != nil {
log.Errorf("Could not read existing resolv.conf")
}
content := fmt.Sprintf(fileGeneratedResolvConfContentFormat, fileDefaultResolvConfBackupLocation, config.serverIP, searchDomains, string(originalContent))
err = writeDNSConfig(content, defaultResolvConfPath, f.originalPerms) err = writeDNSConfig(content, defaultResolvConfPath, f.originalPerms)
if err != nil { if err != nil {
err = f.restore() err = f.restore()

View File

@@ -182,11 +182,12 @@ func (s *systemConfigurator) addDNSState(state, domains, dnsServer string, port
} }
func (s *systemConfigurator) addDNSSetupForAll(dnsServer string, port int) error { func (s *systemConfigurator) addDNSSetupForAll(dnsServer string, port int) error {
primaryServiceKey, existingNameserver := s.getPrimaryService() primaryServiceKey := s.getPrimaryService()
if primaryServiceKey == "" { if primaryServiceKey == "" {
return fmt.Errorf("couldn't find the primary service key") return fmt.Errorf("couldn't find the primary service key")
} }
err := s.addDNSSetup(getKeyWithInput(primaryServiceSetupKeyFormat, primaryServiceKey), dnsServer, port, existingNameserver)
err := s.addDNSSetup(getKeyWithInput(primaryServiceSetupKeyFormat, primaryServiceKey), dnsServer, port)
if err != nil { if err != nil {
return err return err
} }
@@ -195,32 +196,27 @@ func (s *systemConfigurator) addDNSSetupForAll(dnsServer string, port int) error
return nil return nil
} }
func (s *systemConfigurator) getPrimaryService() (string, string) { func (s *systemConfigurator) getPrimaryService() string {
line := buildCommandLine("show", globalIPv4State, "") line := buildCommandLine("show", globalIPv4State, "")
stdinCommands := wrapCommand(line) stdinCommands := wrapCommand(line)
b, err := runSystemConfigCommand(stdinCommands) b, err := runSystemConfigCommand(stdinCommands)
if err != nil { if err != nil {
log.Error("got error while sending the command: ", err) log.Error("got error while sending the command: ", err)
return "", "" return ""
} }
scanner := bufio.NewScanner(bytes.NewReader(b)) scanner := bufio.NewScanner(bytes.NewReader(b))
primaryService := ""
router := ""
for scanner.Scan() { for scanner.Scan() {
text := scanner.Text() text := scanner.Text()
if strings.Contains(text, "PrimaryService") { if strings.Contains(text, "PrimaryService") {
primaryService = strings.TrimSpace(strings.Split(text, ":")[1]) return strings.TrimSpace(strings.Split(text, ":")[1])
}
if strings.Contains(text, "Router") {
router = strings.TrimSpace(strings.Split(text, ":")[1])
} }
} }
return primaryService, router return ""
} }
func (s *systemConfigurator) addDNSSetup(setupKey, dnsServer string, port int, existingDNSServer string) error { func (s *systemConfigurator) addDNSSetup(setupKey, dnsServer string, port int) error {
lines := buildAddCommandLine(keySupplementalMatchDomainsNoSearch, digitSymbol+strconv.Itoa(0)) lines := buildAddCommandLine(keySupplementalMatchDomainsNoSearch, digitSymbol+strconv.Itoa(0))
lines += buildAddCommandLine(keyServerAddresses, arraySymbol+dnsServer+" "+existingDNSServer) lines += buildAddCommandLine(keyServerAddresses, arraySymbol+dnsServer)
lines += buildAddCommandLine(keyServerPort, digitSymbol+strconv.Itoa(port)) lines += buildAddCommandLine(keyServerPort, digitSymbol+strconv.Itoa(port))
addDomainCommand := buildCreateStateWithOperation(setupKey, lines) addDomainCommand := buildCreateStateWithOperation(setupKey, lines)
stdinCommands := wrapCommand(addDomainCommand) stdinCommands := wrapCommand(addDomainCommand)

View File

@@ -4,7 +4,6 @@ package dns
import ( import (
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"strings" "strings"
@@ -60,11 +59,7 @@ func (r *resolvconf) applyDNSConfig(config hostDNSConfig) error {
appendedDomains++ appendedDomains++
} }
originalContent, err := os.ReadFile(fileDefaultResolvConfBackupLocation) content := fmt.Sprintf(fileGeneratedResolvConfContentFormat, fileDefaultResolvConfBackupLocation, config.serverIP, searchDomains)
if err != nil {
log.Errorf("Could not read existing resolv.conf")
}
content := fmt.Sprintf(fileGeneratedResolvConfContentFormat, fileDefaultResolvConfBackupLocation, config.serverIP, searchDomains, string(originalContent))
err = r.applyConfig(content) err = r.applyConfig(content)
if err != nil { if err != nil {

View File

@@ -238,7 +238,7 @@ func (s *DefaultServer) applyConfiguration(update nbdns.Config) error {
hostUpdate := s.currentConfig hostUpdate := s.currentConfig
if s.service.RuntimePort() != defaultPort && !s.hostManager.supportCustomPort() { if s.service.RuntimePort() != defaultPort && !s.hostManager.supportCustomPort() {
log.Warnf("the DNS manager of this peer doesn't support custom port. Disabling primary DNS setup. " + log.Warnf("the DNS manager of this peer doesn't support custom port. Disabling primary DNS setup. " +
"Learn more at: https://docs.netbird.io/how-to/manage-dns-in-your-network#local-resolver") "Learn more at: https://netbird.io/docs/how-to-guides/nameservers#local-resolver")
hostUpdate.routeAll = false hostUpdate.routeAll = false
} }

View File

@@ -777,7 +777,7 @@ func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) {
newNet, err := stdnet.NewNet(nil) newNet, err := stdnet.NewNet(nil)
if err != nil { if err != nil {
t.Fatalf("create stdnet: %v", err) t.Fatalf("create stdnet: %v", err)
return nil, err return nil, nil
} }
wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.2/24", iface.DefaultMTU, nil, newNet) wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.2/24", iface.DefaultMTU, nil, newNet)

View File

@@ -11,9 +11,6 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/internal/ebpf"
ebpfMgr "github.com/netbirdio/netbird/client/internal/ebpf/manager"
) )
const ( const (
@@ -27,11 +24,10 @@ type serviceViaListener struct {
dnsMux *dns.ServeMux dnsMux *dns.ServeMux
customAddr *netip.AddrPort customAddr *netip.AddrPort
server *dns.Server server *dns.Server
listenIP string runtimeIP string
listenPort int runtimePort int
listenerIsRunning bool listenerIsRunning bool
listenerFlagLock sync.Mutex listenerFlagLock sync.Mutex
ebpfService ebpfMgr.Manager
} }
func newServiceViaListener(wgIface WGIface, customAddr *netip.AddrPort) *serviceViaListener { func newServiceViaListener(wgIface WGIface, customAddr *netip.AddrPort) *serviceViaListener {
@@ -47,7 +43,6 @@ func newServiceViaListener(wgIface WGIface, customAddr *netip.AddrPort) *service
UDPSize: 65535, UDPSize: 65535,
}, },
} }
return s return s
} }
@@ -60,21 +55,13 @@ func (s *serviceViaListener) Listen() error {
} }
var err error var err error
s.listenIP, s.listenPort, err = s.evalListenAddress() s.runtimeIP, s.runtimePort, err = s.evalRuntimeAddress()
if err != nil { if err != nil {
log.Errorf("failed to eval runtime address: %s", err) log.Errorf("failed to eval runtime address: %s", err)
return err return err
} }
s.server.Addr = fmt.Sprintf("%s:%d", s.listenIP, s.listenPort) s.server.Addr = fmt.Sprintf("%s:%d", s.runtimeIP, s.runtimePort)
if s.shouldApplyPortFwd() {
s.ebpfService = ebpf.GetEbpfManagerInstance()
err = s.ebpfService.LoadDNSFwd(s.listenIP, s.listenPort)
if err != nil {
log.Warnf("failed to load DNS port forwarder, custom port may not work well on some Linux operating systems: %s", err)
s.ebpfService = nil
}
}
log.Debugf("starting dns on %s", s.server.Addr) log.Debugf("starting dns on %s", s.server.Addr)
go func() { go func() {
s.setListenerStatus(true) s.setListenerStatus(true)
@@ -82,10 +69,9 @@ func (s *serviceViaListener) Listen() error {
err := s.server.ListenAndServe() err := s.server.ListenAndServe()
if err != nil { if err != nil {
log.Errorf("dns server running with %d port returned an error: %v. Will not retry", s.listenPort, err) log.Errorf("dns server running with %d port returned an error: %v. Will not retry", s.runtimePort, err)
} }
}() }()
return nil return nil
} }
@@ -104,13 +90,6 @@ func (s *serviceViaListener) Stop() {
if err != nil { if err != nil {
log.Errorf("stopping dns server listener returned an error: %v", err) log.Errorf("stopping dns server listener returned an error: %v", err)
} }
if s.ebpfService != nil {
err = s.ebpfService.FreeDNSFwd()
if err != nil {
log.Errorf("stopping traffic forwarder returned an error: %v", err)
}
}
} }
func (s *serviceViaListener) RegisterMux(pattern string, handler dns.Handler) { func (s *serviceViaListener) RegisterMux(pattern string, handler dns.Handler) {
@@ -122,18 +101,11 @@ func (s *serviceViaListener) DeregisterMux(pattern string) {
} }
func (s *serviceViaListener) RuntimePort() int { func (s *serviceViaListener) RuntimePort() int {
s.listenerFlagLock.Lock() return s.runtimePort
defer s.listenerFlagLock.Unlock()
if s.ebpfService != nil {
return defaultPort
} else {
return s.listenPort
}
} }
func (s *serviceViaListener) RuntimeIP() string { func (s *serviceViaListener) RuntimeIP() string {
return s.listenIP return s.runtimeIP
} }
func (s *serviceViaListener) setListenerStatus(running bool) { func (s *serviceViaListener) setListenerStatus(running bool) {
@@ -164,30 +136,10 @@ func (s *serviceViaListener) getFirstListenerAvailable() (string, int, error) {
return "", 0, fmt.Errorf("unable to find an unused ip and port combination. IPs tested: %v and ports %v", ips, ports) return "", 0, fmt.Errorf("unable to find an unused ip and port combination. IPs tested: %v and ports %v", ips, ports)
} }
func (s *serviceViaListener) evalListenAddress() (string, int, error) { func (s *serviceViaListener) evalRuntimeAddress() (string, int, error) {
if s.customAddr != nil { if s.customAddr != nil {
return s.customAddr.Addr().String(), int(s.customAddr.Port()), nil return s.customAddr.Addr().String(), int(s.customAddr.Port()), nil
} }
return s.getFirstListenerAvailable() return s.getFirstListenerAvailable()
} }
// shouldApplyPortFwd decides whether to apply eBPF program to capture DNS traffic on port 53.
// This is needed because on some operating systems if we start a DNS server not on a default port 53, the domain name
// resolution won't work.
// So, in case we are running on Linux and picked a non-default port (53) we should fall back to the eBPF solution that will capture
// traffic on port 53 and forward it to a local DNS server running on 5053.
func (s *serviceViaListener) shouldApplyPortFwd() bool {
if runtime.GOOS != "linux" {
return false
}
if s.customAddr != nil {
return false
}
if s.listenPort == defaultPort {
return false
}
return true
}

View File

@@ -1,51 +0,0 @@
package ebpf
import (
"encoding/binary"
"net"
log "github.com/sirupsen/logrus"
)
const (
mapKeyDNSIP uint32 = 0
mapKeyDNSPort uint32 = 1
)
func (tf *GeneralManager) LoadDNSFwd(ip string, dnsPort int) error {
log.Debugf("load ebpf DNS forwarder: address: %s:%d", ip, dnsPort)
tf.lock.Lock()
defer tf.lock.Unlock()
err := tf.loadXdp()
if err != nil {
return err
}
err = tf.bpfObjs.NbMapDnsIp.Put(mapKeyDNSIP, ip2int(ip))
if err != nil {
return err
}
err = tf.bpfObjs.NbMapDnsPort.Put(mapKeyDNSPort, uint16(dnsPort))
if err != nil {
return err
}
tf.setFeatureFlag(featureFlagDnsForwarder)
err = tf.bpfObjs.NbFeatures.Put(mapKeyFeatures, tf.featureFlags)
if err != nil {
return err
}
return nil
}
func (tf *GeneralManager) FreeDNSFwd() error {
log.Debugf("free ebpf DNS forwarder")
return tf.unsetFeatureFlag(featureFlagDnsForwarder)
}
func ip2int(ipString string) uint32 {
ip := net.ParseIP(ipString)
return binary.BigEndian.Uint32(ip.To4())
}

View File

@@ -1,116 +0,0 @@
package ebpf
import (
_ "embed"
"net"
"sync"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/rlimit"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/internal/ebpf/manager"
)
const (
mapKeyFeatures uint32 = 0
featureFlagWGProxy = 0b00000001
featureFlagDnsForwarder = 0b00000010
)
var (
singleton manager.Manager
singletonLock = &sync.Mutex{}
)
// required packages libbpf-dev, libc6-dev-i386-amd64-cross
// GeneralManager is used to load multiple eBPF programs with a custom check (if then) done in prog.c
// The manager simply adds a feature (byte) of each program to a map that is shared between the userspace and kernel.
// When packet arrives, the C code checks for each feature (if it is set) and executes each enabled program (e.g., dns_fwd.c and wg_proxy.c).
//
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang-14 bpf src/prog.c -- -I /usr/x86_64-linux-gnu/include
type GeneralManager struct {
lock sync.Mutex
link link.Link
featureFlags uint16
bpfObjs bpfObjects
}
// GetEbpfManagerInstance return a static eBpf Manager instance
func GetEbpfManagerInstance() manager.Manager {
singletonLock.Lock()
defer singletonLock.Unlock()
if singleton != nil {
return singleton
}
singleton = &GeneralManager{}
return singleton
}
func (tf *GeneralManager) setFeatureFlag(feature uint16) {
tf.featureFlags = tf.featureFlags | feature
}
func (tf *GeneralManager) loadXdp() error {
if tf.link != nil {
return nil
}
// it required for Docker
err := rlimit.RemoveMemlock()
if err != nil {
return err
}
iFace, err := net.InterfaceByName("lo")
if err != nil {
return err
}
// load pre-compiled programs into the kernel.
err = loadBpfObjects(&tf.bpfObjs, nil)
if err != nil {
return err
}
tf.link, err = link.AttachXDP(link.XDPOptions{
Program: tf.bpfObjs.NbXdpProg,
Interface: iFace.Index,
})
if err != nil {
_ = tf.bpfObjs.Close()
tf.link = nil
return err
}
return nil
}
func (tf *GeneralManager) unsetFeatureFlag(feature uint16) error {
tf.lock.Lock()
defer tf.lock.Unlock()
tf.featureFlags &^= feature
if tf.link == nil {
return nil
}
if tf.featureFlags == 0 {
return tf.close()
}
return tf.bpfObjs.NbFeatures.Put(mapKeyFeatures, tf.featureFlags)
}
func (tf *GeneralManager) close() error {
log.Debugf("detach ebpf program ")
err := tf.bpfObjs.Close()
if err != nil {
log.Warnf("failed to close eBpf objects: %s", err)
}
err = tf.link.Close()
tf.link = nil
return err
}

View File

@@ -1,40 +0,0 @@
package ebpf
import (
"testing"
)
func TestManager_setFeatureFlag(t *testing.T) {
mgr := GeneralManager{}
mgr.setFeatureFlag(featureFlagWGProxy)
if mgr.featureFlags != 1 {
t.Errorf("invalid faeture state")
}
mgr.setFeatureFlag(featureFlagDnsForwarder)
if mgr.featureFlags != 3 {
t.Errorf("invalid faeture state")
}
}
func TestManager_unsetFeatureFlag(t *testing.T) {
mgr := GeneralManager{}
mgr.setFeatureFlag(featureFlagWGProxy)
mgr.setFeatureFlag(featureFlagDnsForwarder)
err := mgr.unsetFeatureFlag(featureFlagWGProxy)
if err != nil {
t.Errorf("unexpected error: %s", err)
}
if mgr.featureFlags != 2 {
t.Errorf("invalid faeture state, expected: %d, got: %d", 2, mgr.featureFlags)
}
err = mgr.unsetFeatureFlag(featureFlagDnsForwarder)
if err != nil {
t.Errorf("unexpected error: %s", err)
}
if mgr.featureFlags != 0 {
t.Errorf("invalid faeture state, expected: %d, got: %d", 0, mgr.featureFlags)
}
}

View File

@@ -1,64 +0,0 @@
const __u32 map_key_dns_ip = 0;
const __u32 map_key_dns_port = 1;
struct bpf_map_def SEC("maps") nb_map_dns_ip = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(__u32),
.max_entries = 10,
};
struct bpf_map_def SEC("maps") nb_map_dns_port = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(__u16),
.max_entries = 10,
};
__be32 dns_ip = 0;
__be16 dns_port = 0;
// 13568 is 53 in big endian
__be16 GENERAL_DNS_PORT = 13568;
bool read_settings() {
__u16 *port_value;
__u32 *ip_value;
// read dns ip
ip_value = bpf_map_lookup_elem(&nb_map_dns_ip, &map_key_dns_ip);
if(!ip_value) {
return false;
}
dns_ip = htonl(*ip_value);
// read dns port
port_value = bpf_map_lookup_elem(&nb_map_dns_port, &map_key_dns_port);
if (!port_value) {
return false;
}
dns_port = htons(*port_value);
return true;
}
int xdp_dns_fwd(struct iphdr *ip, struct udphdr *udp) {
if (dns_port == 0) {
if(!read_settings()){
return XDP_PASS;
}
bpf_printk("dns port: %d", ntohs(dns_port));
bpf_printk("dns ip: %d", ntohl(dns_ip));
}
if (udp->dest == GENERAL_DNS_PORT && ip->daddr == dns_ip) {
udp->dest = dns_port;
return XDP_PASS;
}
if (udp->source == dns_port && ip->saddr == dns_ip) {
udp->source = GENERAL_DNS_PORT;
return XDP_PASS;
}
return XDP_PASS;
}

View File

@@ -1,66 +0,0 @@
#include <stdbool.h>
#include <linux/if_ether.h> // ETH_P_IP
#include <linux/udp.h>
#include <linux/ip.h>
#include <netinet/in.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "dns_fwd.c"
#include "wg_proxy.c"
#define bpf_printk(fmt, ...) \
({ \
char ____fmt[] = fmt; \
bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
})
const __u16 flag_feature_wg_proxy = 0b01;
const __u16 flag_feature_dns_fwd = 0b10;
const __u32 map_key_features = 0;
struct bpf_map_def SEC("maps") nb_features = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(__u16),
.max_entries = 10,
};
SEC("xdp")
int nb_xdp_prog(struct xdp_md *ctx) {
__u16 *features;
features = bpf_map_lookup_elem(&nb_features, &map_key_features);
if (!features) {
return XDP_PASS;
}
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
struct iphdr *ip = (data + sizeof(struct ethhdr));
struct udphdr *udp = (data + sizeof(struct ethhdr) + sizeof(struct iphdr));
// return early if not enough data
if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) > data_end){
return XDP_PASS;
}
// skip non IPv4 packages
if (eth->h_proto != htons(ETH_P_IP)) {
return XDP_PASS;
}
// skip non UPD packages
if (ip->protocol != IPPROTO_UDP) {
return XDP_PASS;
}
if (*features & flag_feature_dns_fwd) {
xdp_dns_fwd(ip, udp);
}
if (*features & flag_feature_wg_proxy) {
xdp_wg_proxy(ip, udp);
}
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";

View File

@@ -1,54 +0,0 @@
const __u32 map_key_proxy_port = 0;
const __u32 map_key_wg_port = 1;
struct bpf_map_def SEC("maps") nb_wg_proxy_settings_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(__u16),
.max_entries = 10,
};
__u16 proxy_port = 0;
__u16 wg_port = 0;
bool read_port_settings() {
__u16 *value;
value = bpf_map_lookup_elem(&nb_wg_proxy_settings_map, &map_key_proxy_port);
if (!value) {
return false;
}
proxy_port = *value;
value = bpf_map_lookup_elem(&nb_wg_proxy_settings_map, &map_key_wg_port);
if (!value) {
return false;
}
wg_port = htons(*value);
return true;
}
int xdp_wg_proxy(struct iphdr *ip, struct udphdr *udp) {
if (proxy_port == 0 || wg_port == 0) {
if (!read_port_settings()){
return XDP_PASS;
}
bpf_printk("proxy port: %d, wg port: %d", proxy_port, wg_port);
}
// 2130706433 = 127.0.0.1
if (ip->daddr != htonl(2130706433)) {
return XDP_PASS;
}
if (udp->source != wg_port){
return XDP_PASS;
}
__be16 new_src_port = udp->dest;
__be16 new_dst_port = htons(proxy_port);
udp->dest = new_dst_port;
udp->source = new_src_port;
return XDP_PASS;
}

View File

@@ -1,41 +0,0 @@
package ebpf
import log "github.com/sirupsen/logrus"
const (
mapKeyProxyPort uint32 = 0
mapKeyWgPort uint32 = 1
)
func (tf *GeneralManager) LoadWgProxy(proxyPort, wgPort int) error {
log.Debugf("load ebpf WG proxy")
tf.lock.Lock()
defer tf.lock.Unlock()
err := tf.loadXdp()
if err != nil {
return err
}
err = tf.bpfObjs.NbWgProxySettingsMap.Put(mapKeyProxyPort, uint16(proxyPort))
if err != nil {
return err
}
err = tf.bpfObjs.NbWgProxySettingsMap.Put(mapKeyWgPort, uint16(wgPort))
if err != nil {
return err
}
tf.setFeatureFlag(featureFlagWGProxy)
err = tf.bpfObjs.NbFeatures.Put(mapKeyFeatures, tf.featureFlags)
if err != nil {
return err
}
return nil
}
func (tf *GeneralManager) FreeWGProxy() error {
log.Debugf("free ebpf WG proxy")
return tf.unsetFeatureFlag(featureFlagWGProxy)
}

View File

@@ -1,15 +0,0 @@
//go:build !android
package ebpf
import (
"github.com/netbirdio/netbird/client/internal/ebpf/ebpf"
"github.com/netbirdio/netbird/client/internal/ebpf/manager"
)
// GetEbpfManagerInstance is a wrapper function. This encapsulation is required because if the code import the internal
// ebpf package the Go compiler will include the object files. But it is not supported on Android. It can cause instant
// panic on older Android version.
func GetEbpfManagerInstance() manager.Manager {
return ebpf.GetEbpfManagerInstance()
}

View File

@@ -1,10 +0,0 @@
//go:build !linux || android
package ebpf
import "github.com/netbirdio/netbird/client/internal/ebpf/manager"
// GetEbpfManagerInstance return error because ebpf is not supported on all os
func GetEbpfManagerInstance() manager.Manager {
panic("unsupported os")
}

View File

@@ -1,9 +0,0 @@
package manager
// Manager is used to load multiple eBPF programs. E.g., current DNS programs and WireGuard proxy
type Manager interface {
LoadDNSFwd(ip string, dnsPort int) error
FreeDNSFwd() error
LoadWgProxy(proxyPort, wgPort int) error
FreeWGProxy() error
}

View File

@@ -992,12 +992,14 @@ func (e *Engine) parseNATExternalIPMappings() []string {
log.Warnf("invalid external IP, %s, ignoring external IP mapping '%s'", external, mapping) log.Warnf("invalid external IP, %s, ignoring external IP mapping '%s'", external, mapping)
break break
} }
mappedIP := externalIP.String() if externalIP != nil {
if internalIP != nil { mappedIP := externalIP.String()
mappedIP = mappedIP + "/" + internalIP.String() if internalIP != nil {
mappedIP = mappedIP + "/" + internalIP.String()
}
mappedIPs = append(mappedIPs, mappedIP)
log.Infof("parsed external IP mapping of '%s' as '%s'", mapping, mappedIP)
} }
mappedIPs = append(mappedIPs, mappedIP)
log.Infof("parsed external IP mapping of '%s' as '%s'", mapping, mappedIP)
} }
if len(mappedIPs) != len(e.config.NATExternalIPs) { if len(mappedIPs) != len(e.config.NATExternalIPs) {
log.Warnf("one or more external IP mappings failed to parse, ignoring all mappings") log.Warnf("one or more external IP mappings failed to parse, ignoring all mappings")

View File

@@ -1046,15 +1046,15 @@ func startManagement(dataDir string) (*grpc.Server, string, error) {
peersUpdateManager := server.NewPeersUpdateManager() peersUpdateManager := server.NewPeersUpdateManager()
eventStore := &activity.InMemoryEventStore{} eventStore := &activity.InMemoryEventStore{}
if err != nil { if err != nil {
return nil, "", err return nil, "", nil
} }
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
eventStore, false) eventStore)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig) turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, nil, nil) mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, nil)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }

View File

@@ -17,7 +17,6 @@ type notifier struct {
listener Listener listener Listener
currentClientState bool currentClientState bool
lastNotification int lastNotification int
lastNumberOfPeers int
} }
func newNotifier() *notifier { func newNotifier() *notifier {
@@ -30,7 +29,6 @@ func (n *notifier) setListener(listener Listener) {
n.serverStateLock.Lock() n.serverStateLock.Lock()
n.notifyListener(listener, n.lastNotification) n.notifyListener(listener, n.lastNotification)
listener.OnPeersListChanged(n.lastNumberOfPeers)
n.serverStateLock.Unlock() n.serverStateLock.Unlock()
n.listener = listener n.listener = listener
@@ -61,7 +59,7 @@ func (n *notifier) clientStart() {
n.serverStateLock.Lock() n.serverStateLock.Lock()
defer n.serverStateLock.Unlock() defer n.serverStateLock.Unlock()
n.currentClientState = true n.currentClientState = true
n.lastNotification = stateConnecting n.lastNotification = stateConnected
n.notify(n.lastNotification) n.notify(n.lastNotification)
} }
@@ -114,7 +112,7 @@ func (n *notifier) calculateState(managementConn, signalConn bool) int {
return stateConnected return stateConnected
} }
if !managementConn && !signalConn && !n.currentClientState { if !managementConn && !signalConn {
return stateDisconnected return stateDisconnected
} }
@@ -126,7 +124,6 @@ func (n *notifier) calculateState(managementConn, signalConn bool) int {
} }
func (n *notifier) peerListChanged(numOfPeers int) { func (n *notifier) peerListChanged(numOfPeers int) {
n.lastNumberOfPeers = numOfPeers
n.listenersLock.Lock() n.listenersLock.Lock()
defer n.listenersLock.Unlock() defer n.listenersLock.Unlock()
if n.listener == nil { if n.listener == nil {

View File

@@ -353,13 +353,9 @@ func (d *Status) onConnectionChanged() {
} }
func (d *Status) notifyPeerListChanged() { func (d *Status) notifyPeerListChanged() {
d.notifier.peerListChanged(d.numOfPeers()) d.notifier.peerListChanged(len(d.peers) + len(d.offlinePeers))
} }
func (d *Status) notifyAddressChanged() { func (d *Status) notifyAddressChanged() {
d.notifier.localAddressChanged(d.localPeer.FQDN, d.localPeer.IP) d.notifier.localAddressChanged(d.localPeer.FQDN, d.localPeer.IP)
} }
func (d *Status) numOfPeers() int {
return len(d.peers) + len(d.offlinePeers)
}

View File

@@ -155,10 +155,7 @@ func (c *clientNetwork) startPeersStatusChangeWatcher() {
func (c *clientNetwork) removeRouteFromWireguardPeer(peerKey string) error { func (c *clientNetwork) removeRouteFromWireguardPeer(peerKey string) error {
state, err := c.statusRecorder.GetPeer(peerKey) state, err := c.statusRecorder.GetPeer(peerKey)
if err != nil { if err != nil || state.ConnStatus != peer.StatusConnected {
return err
}
if state.ConnStatus != peer.StatusConnected {
return nil return nil
} }

View File

@@ -7,8 +7,6 @@ import (
"fmt" "fmt"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/internal/checkfw"
) )
const ( const (
@@ -28,20 +26,15 @@ func genKey(format string, input string) string {
return fmt.Sprintf(format, input) return fmt.Sprintf(format, input)
} }
// newFirewall if supported, returns an iptables manager, otherwise returns a nftables manager // NewFirewall if supported, returns an iptables manager, otherwise returns a nftables manager
func newFirewall(parentCTX context.Context) (firewallManager, error) { func NewFirewall(parentCTX context.Context) firewallManager {
checkResult := checkfw.Check() manager, err := newNFTablesManager(parentCTX)
switch checkResult { if err == nil {
case checkfw.IPTABLES, checkfw.IPTABLESWITHV6: log.Debugf("nftables firewall manager will be used")
log.Debug("creating an iptables firewall manager for route rules") return manager
ipv6Supported := checkResult == checkfw.IPTABLESWITHV6
return newIptablesManager(parentCTX, ipv6Supported)
case checkfw.NFTABLES:
log.Info("creating an nftables firewall manager for route rules")
return newNFTablesManager(parentCTX), nil
} }
log.Debugf("fallback to iptables firewall manager: %s", err)
return nil, fmt.Errorf("couldn't initialize nftables or iptables clients. Using a dummy firewall manager for route rules") return newIptablesManager(parentCTX)
} }
func getInPair(pair routerPair) routerPair { func getInPair(pair routerPair) routerPair {

View File

@@ -3,13 +3,24 @@
package routemanager package routemanager
import ( import "context"
"context"
"fmt"
"runtime"
)
// newFirewall returns a nil manager type unimplementedFirewall struct{}
func newFirewall(context.Context) (firewallManager, error) {
return nil, fmt.Errorf("firewall not supported on %s", runtime.GOOS) func (unimplementedFirewall) RestoreOrCreateContainers() error {
return nil
}
func (unimplementedFirewall) InsertRoutingRules(pair routerPair) error {
return nil
}
func (unimplementedFirewall) RemoveRoutingRules(pair routerPair) error {
return nil
}
func (unimplementedFirewall) CleanRoutingRules() {
}
// NewFirewall returns an unimplemented Firewall manager
func NewFirewall(parentCtx context.Context) firewallManager {
return unimplementedFirewall{}
} }

View File

@@ -49,28 +49,26 @@ type iptablesManager struct {
mux sync.Mutex mux sync.Mutex
} }
func newIptablesManager(parentCtx context.Context, ipv6Supported bool) (*iptablesManager, error) { func newIptablesManager(parentCtx context.Context) *iptablesManager {
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) ctx, cancel := context.WithCancel(parentCtx)
if err != nil { ipv4Client, _ := iptables.NewWithProtocol(iptables.ProtocolIPv4)
return nil, fmt.Errorf("failed to initialize iptables for ipv4: %s", err) if !isIptablesClientAvailable(ipv4Client) {
log.Infof("iptables is missing for ipv4")
ipv4Client = nil
}
ipv6Client, _ := iptables.NewWithProtocol(iptables.ProtocolIPv6)
if !isIptablesClientAvailable(ipv6Client) {
log.Infof("iptables is missing for ipv6")
ipv6Client = nil
} }
ctx, cancel := context.WithCancel(parentCtx) return &iptablesManager{
manager := &iptablesManager{
ctx: ctx, ctx: ctx,
stop: cancel, stop: cancel,
ipv4Client: ipv4Client, ipv4Client: ipv4Client,
ipv6Client: ipv6Client,
rules: make(map[string]map[string][]string), rules: make(map[string]map[string][]string),
} }
if ipv6Supported {
manager.ipv6Client, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
if err != nil {
log.Warnf("failed to initialize iptables for ipv6: %s. Routes for this protocol won't be applied.", err)
}
}
return manager, nil
} }
// CleanRoutingRules cleans existing iptables resources that we created by the agent // CleanRoutingRules cleans existing iptables resources that we created by the agent
@@ -393,10 +391,6 @@ func (i *iptablesManager) insertRoutingRule(keyFormat, table, chain, jump string
ipVersion = ipv6 ipVersion = ipv6
} }
if iptablesClient == nil {
return fmt.Errorf("unable to insert iptables routing rules. Iptables client is not initialized")
}
ruleKey := genKey(keyFormat, pair.ID) ruleKey := genKey(keyFormat, pair.ID)
rule := genRuleSpec(jump, ruleKey, pair.source, pair.destination) rule := genRuleSpec(jump, ruleKey, pair.source, pair.destination)
existingRule, found := i.rules[ipVersion][ruleKey] existingRule, found := i.rules[ipVersion][ruleKey]
@@ -461,10 +455,6 @@ func (i *iptablesManager) removeRoutingRule(keyFormat, table, chain string, pair
ipVersion = ipv6 ipVersion = ipv6
} }
if iptablesClient == nil {
return fmt.Errorf("unable to remove iptables routing rules. Iptables client is not initialized")
}
ruleKey := genKey(keyFormat, pair.ID) ruleKey := genKey(keyFormat, pair.ID)
existingRule, found := i.rules[ipVersion][ruleKey] existingRule, found := i.rules[ipVersion][ruleKey]
if found { if found {
@@ -485,3 +475,8 @@ func getIptablesRuleType(table string) string {
} }
return ruleType return ruleType
} }
func isIptablesClientAvailable(client *iptables.IPTables) bool {
_, err := client.ListChains("filter")
return err == nil
}

View File

@@ -16,12 +16,11 @@ func TestIptablesManager_RestoreOrCreateContainers(t *testing.T) {
t.SkipNow() t.SkipNow()
} }
manager, err := newIptablesManager(context.TODO(), true) manager := newIptablesManager(context.TODO())
require.NoError(t, err, "should return a valid iptables manager")
defer manager.CleanRoutingRules() defer manager.CleanRoutingRules()
err = manager.RestoreOrCreateContainers() err := manager.RestoreOrCreateContainers()
require.NoError(t, err, "shouldn't return error") require.NoError(t, err, "shouldn't return error")
require.Len(t, manager.rules, 2, "should have created maps for ipv4 and ipv6") require.Len(t, manager.rules, 2, "should have created maps for ipv4 and ipv6")

View File

@@ -27,7 +27,7 @@ type DefaultManager struct {
stop context.CancelFunc stop context.CancelFunc
mux sync.Mutex mux sync.Mutex
clientNetworks map[string]*clientNetwork clientNetworks map[string]*clientNetwork
serverRouter serverRouter serverRouter *serverRouter
statusRecorder *peer.Status statusRecorder *peer.Status
wgInterface *iface.WGIface wgInterface *iface.WGIface
pubKey string pubKey string
@@ -36,17 +36,13 @@ type DefaultManager struct {
// NewManager returns a new route manager // NewManager returns a new route manager
func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface, statusRecorder *peer.Status, initialRoutes []*route.Route) *DefaultManager { func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface, statusRecorder *peer.Status, initialRoutes []*route.Route) *DefaultManager {
srvRouter, err := newServerRouter(ctx, wgInterface)
if err != nil {
log.Errorf("server router is not supported: %s", err)
}
mCTX, cancel := context.WithCancel(ctx) mCTX, cancel := context.WithCancel(ctx)
dm := &DefaultManager{ dm := &DefaultManager{
ctx: mCTX, ctx: mCTX,
stop: cancel, stop: cancel,
clientNetworks: make(map[string]*clientNetwork), clientNetworks: make(map[string]*clientNetwork),
serverRouter: srvRouter, serverRouter: newServerRouter(ctx, wgInterface),
statusRecorder: statusRecorder, statusRecorder: statusRecorder,
wgInterface: wgInterface, wgInterface: wgInterface,
pubKey: pubKey, pubKey: pubKey,
@@ -63,9 +59,7 @@ func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface,
// Stop stops the manager watchers and clean firewall rules // Stop stops the manager watchers and clean firewall rules
func (m *DefaultManager) Stop() { func (m *DefaultManager) Stop() {
m.stop() m.stop()
if m.serverRouter != nil { m.serverRouter.cleanUp()
m.serverRouter.cleanUp()
}
m.ctx = nil m.ctx = nil
} }
@@ -83,12 +77,9 @@ func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Ro
m.updateClientNetworks(updateSerial, newClientRoutesIDMap) m.updateClientNetworks(updateSerial, newClientRoutesIDMap)
m.notifier.onNewRoutes(newClientRoutesIDMap) m.notifier.onNewRoutes(newClientRoutesIDMap)
err := m.serverRouter.updateRoutes(newServerRoutesMap)
if m.serverRouter != nil { if err != nil {
err := m.serverRouter.updateRoutes(newServerRoutesMap) return err
if err != nil {
return err
}
} }
return nil return nil

View File

@@ -3,12 +3,11 @@ package routemanager
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/pion/transport/v2/stdnet"
"net/netip" "net/netip"
"runtime" "runtime"
"testing" "testing"
"github.com/pion/transport/v2/stdnet"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/client/internal/peer" "github.com/netbirdio/netbird/client/internal/peer"
@@ -31,7 +30,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
inputInitRoutes []*route.Route inputInitRoutes []*route.Route
inputRoutes []*route.Route inputRoutes []*route.Route
inputSerial uint64 inputSerial uint64
removeSrvRouter bool shouldCheckServerRoutes bool
serverRoutesExpected int serverRoutesExpected int
clientNetworkWatchersExpected int clientNetworkWatchersExpected int
}{ }{
@@ -88,6 +87,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
}, },
}, },
inputSerial: 1, inputSerial: 1,
shouldCheckServerRoutes: runtime.GOOS == "linux",
serverRoutesExpected: 2, serverRoutesExpected: 2,
clientNetworkWatchersExpected: 0, clientNetworkWatchersExpected: 0,
}, },
@@ -116,38 +116,10 @@ func TestManagerUpdateRoutes(t *testing.T) {
}, },
}, },
inputSerial: 1, inputSerial: 1,
shouldCheckServerRoutes: runtime.GOOS == "linux",
serverRoutesExpected: 1, serverRoutesExpected: 1,
clientNetworkWatchersExpected: 1, clientNetworkWatchersExpected: 1,
}, },
{
name: "Should Create 1 Route For Client and Skip Server Route On Empty Server Router",
inputRoutes: []*route.Route{
{
ID: "a",
NetID: "routeA",
Peer: localPeerKey,
Network: netip.MustParsePrefix("100.64.30.250/30"),
NetworkType: route.IPv4Network,
Metric: 9999,
Masquerade: false,
Enabled: true,
},
{
ID: "b",
NetID: "routeB",
Peer: remotePeerKey1,
Network: netip.MustParsePrefix("8.8.9.9/32"),
NetworkType: route.IPv4Network,
Metric: 9999,
Masquerade: false,
Enabled: true,
},
},
inputSerial: 1,
removeSrvRouter: true,
serverRoutesExpected: 0,
clientNetworkWatchersExpected: 1,
},
{ {
name: "Should Create 1 HA Route and 1 Standalone", name: "Should Create 1 HA Route and 1 Standalone",
inputRoutes: []*route.Route{ inputRoutes: []*route.Route{
@@ -202,6 +174,25 @@ func TestManagerUpdateRoutes(t *testing.T) {
inputSerial: 1, inputSerial: 1,
clientNetworkWatchersExpected: 0, clientNetworkWatchersExpected: 0,
}, },
{
name: "No Server Routes Should Be Added To Non Linux",
inputRoutes: []*route.Route{
{
ID: "a",
NetID: "routeA",
Peer: localPeerKey,
Network: netip.MustParsePrefix("1.2.3.4/32"),
NetworkType: route.IPv4Network,
Metric: 9999,
Masquerade: false,
Enabled: true,
},
},
inputSerial: 1,
shouldCheckServerRoutes: runtime.GOOS != "linux",
serverRoutesExpected: 0,
clientNetworkWatchersExpected: 0,
},
{ {
name: "Remove 1 Client Route", name: "Remove 1 Client Route",
inputInitRoutes: []*route.Route{ inputInitRoutes: []*route.Route{
@@ -344,6 +335,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
}, },
inputRoutes: []*route.Route{}, inputRoutes: []*route.Route{},
inputSerial: 1, inputSerial: 1,
shouldCheckServerRoutes: true,
serverRoutesExpected: 0, serverRoutesExpected: 0,
clientNetworkWatchersExpected: 0, clientNetworkWatchersExpected: 0,
}, },
@@ -392,6 +384,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
}, },
}, },
inputSerial: 1, inputSerial: 1,
shouldCheckServerRoutes: runtime.GOOS == "linux",
serverRoutesExpected: 2, serverRoutesExpected: 2,
clientNetworkWatchersExpected: 1, clientNetworkWatchersExpected: 1,
}, },
@@ -416,10 +409,6 @@ func TestManagerUpdateRoutes(t *testing.T) {
routeManager := NewManager(ctx, localPeerKey, wgInterface, statusRecorder, nil) routeManager := NewManager(ctx, localPeerKey, wgInterface, statusRecorder, nil)
defer routeManager.Stop() defer routeManager.Stop()
if testCase.removeSrvRouter {
routeManager.serverRouter = nil
}
if len(testCase.inputInitRoutes) > 0 { if len(testCase.inputInitRoutes) > 0 {
err = routeManager.UpdateRoutes(testCase.inputSerial, testCase.inputRoutes) err = routeManager.UpdateRoutes(testCase.inputSerial, testCase.inputRoutes)
require.NoError(t, err, "should update routes with init routes") require.NoError(t, err, "should update routes with init routes")
@@ -430,9 +419,8 @@ func TestManagerUpdateRoutes(t *testing.T) {
require.Len(t, routeManager.clientNetworks, testCase.clientNetworkWatchersExpected, "client networks size should match") require.Len(t, routeManager.clientNetworks, testCase.clientNetworkWatchersExpected, "client networks size should match")
if runtime.GOOS == "linux" && routeManager.serverRouter != nil { if testCase.shouldCheckServerRoutes {
sr := routeManager.serverRouter.(*defaultServerRouter) require.Len(t, routeManager.serverRouter.routes, testCase.serverRoutesExpected, "server networks size should match")
require.Len(t, sr.routes, testCase.serverRoutesExpected, "server networks size should match")
} }
}) })
} }

View File

@@ -86,10 +86,10 @@ type nftablesManager struct {
mux sync.Mutex mux sync.Mutex
} }
func newNFTablesManager(parentCtx context.Context) *nftablesManager { func newNFTablesManager(parentCtx context.Context) (*nftablesManager, error) {
ctx, cancel := context.WithCancel(parentCtx) ctx, cancel := context.WithCancel(parentCtx)
return &nftablesManager{ mgr := &nftablesManager{
ctx: ctx, ctx: ctx,
stop: cancel, stop: cancel,
conn: &nftables.Conn{}, conn: &nftables.Conn{},
@@ -97,6 +97,18 @@ func newNFTablesManager(parentCtx context.Context) *nftablesManager {
rules: make(map[string]*nftables.Rule), rules: make(map[string]*nftables.Rule),
defaultForwardRules: make([]*nftables.Rule, 2), defaultForwardRules: make([]*nftables.Rule, 2),
} }
err := mgr.isSupported()
if err != nil {
return nil, err
}
err = mgr.readFilterTable()
if err != nil {
return nil, err
}
return mgr, nil
} }
// CleanRoutingRules cleans existing nftables rules from the system // CleanRoutingRules cleans existing nftables rules from the system
@@ -135,10 +147,6 @@ func (n *nftablesManager) RestoreOrCreateContainers() error {
} }
for _, table := range tables { for _, table := range tables {
if table.Name == "filter" {
n.filterTable = table
continue
}
if table.Name == nftablesTable { if table.Name == nftablesTable {
if table.Family == nftables.TableFamilyIPv4 { if table.Family == nftables.TableFamilyIPv4 {
n.tableIPv4 = table n.tableIPv4 = table
@@ -251,6 +259,21 @@ func (n *nftablesManager) refreshRulesMap() error {
return nil return nil
} }
func (n *nftablesManager) readFilterTable() error {
tables, err := n.conn.ListTables()
if err != nil {
return err
}
for _, t := range tables {
if t.Name == "filter" {
n.filterTable = t
return nil
}
}
return nil
}
func (n *nftablesManager) eraseDefaultForwardRule() error { func (n *nftablesManager) eraseDefaultForwardRule() error {
if n.defaultForwardRules[0] == nil { if n.defaultForwardRules[0] == nil {
return nil return nil
@@ -521,6 +544,14 @@ func (n *nftablesManager) removeRoutingRule(format string, pair routerPair) erro
return nil return nil
} }
func (n *nftablesManager) isSupported() error {
_, err := n.conn.ListChains()
if err != nil {
return fmt.Errorf("nftables is not supported: %s", err)
}
return nil
}
// getPayloadDirectives get expression directives based on ip version and direction // getPayloadDirectives get expression directives based on ip version and direction
func getPayloadDirectives(direction string, isIPv4 bool, isIPv6 bool) (uint32, uint32, []byte) { func getPayloadDirectives(direction string, isIPv4 bool, isIPv6 bool) (uint32, uint32, []byte) {
switch { switch {

View File

@@ -10,23 +10,20 @@ import (
"github.com/google/nftables/expr" "github.com/google/nftables/expr"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/client/internal/checkfw"
) )
func TestNftablesManager_RestoreOrCreateContainers(t *testing.T) { func TestNftablesManager_RestoreOrCreateContainers(t *testing.T) {
if checkfw.Check() != checkfw.NFTABLES { manager, err := newNFTablesManager(context.TODO())
t.Skip("nftables not supported on this OS") if err != nil {
t.Fatalf("failed to create nftables manager: %s", err)
} }
manager := newNFTablesManager(context.TODO())
nftablesTestingClient := &nftables.Conn{} nftablesTestingClient := &nftables.Conn{}
defer manager.CleanRoutingRules() defer manager.CleanRoutingRules()
err := manager.RestoreOrCreateContainers() err = manager.RestoreOrCreateContainers()
require.NoError(t, err, "shouldn't return error") require.NoError(t, err, "shouldn't return error")
require.Len(t, manager.chains, 2, "should have created chains for ipv4 and ipv6") require.Len(t, manager.chains, 2, "should have created chains for ipv4 and ipv6")
@@ -129,19 +126,19 @@ func TestNftablesManager_RestoreOrCreateContainers(t *testing.T) {
} }
func TestNftablesManager_InsertRoutingRules(t *testing.T) { func TestNftablesManager_InsertRoutingRules(t *testing.T) {
if checkfw.Check() != checkfw.NFTABLES {
t.Skip("nftables not supported on this OS")
}
for _, testCase := range insertRuleTestCases { for _, testCase := range insertRuleTestCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
manager := newNFTablesManager(context.TODO()) manager, err := newNFTablesManager(context.TODO())
if err != nil {
t.Fatalf("failed to create nftables manager: %s", err)
}
nftablesTestingClient := &nftables.Conn{} nftablesTestingClient := &nftables.Conn{}
defer manager.CleanRoutingRules() defer manager.CleanRoutingRules()
err := manager.RestoreOrCreateContainers() err = manager.RestoreOrCreateContainers()
require.NoError(t, err, "shouldn't return error") require.NoError(t, err, "shouldn't return error")
err = manager.InsertRoutingRules(testCase.inputPair) err = manager.InsertRoutingRules(testCase.inputPair)
@@ -229,19 +226,19 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) {
} }
func TestNftablesManager_RemoveRoutingRules(t *testing.T) { func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
if checkfw.Check() != checkfw.NFTABLES {
t.Skip("nftables not supported on this OS")
}
for _, testCase := range removeRuleTestCases { for _, testCase := range removeRuleTestCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
manager := newNFTablesManager(context.TODO()) manager, err := newNFTablesManager(context.TODO())
if err != nil {
t.Fatalf("failed to create nftables manager: %s", err)
}
nftablesTestingClient := &nftables.Conn{} nftablesTestingClient := &nftables.Conn{}
defer manager.CleanRoutingRules() defer manager.CleanRoutingRules()
err := manager.RestoreOrCreateContainers() err = manager.RestoreOrCreateContainers()
require.NoError(t, err, "shouldn't return error") require.NoError(t, err, "shouldn't return error")
table := manager.tableIPv4 table := manager.tableIPv4

View File

@@ -1,9 +0,0 @@
package routemanager
import "github.com/netbirdio/netbird/route"
type serverRouter interface {
updateRoutes(map[string]*route.Route) error
removeFromServerNetwork(*route.Route) error
cleanUp()
}

View File

@@ -2,11 +2,20 @@ package routemanager
import ( import (
"context" "context"
"fmt"
"github.com/netbirdio/netbird/iface" "github.com/netbirdio/netbird/iface"
"github.com/netbirdio/netbird/route"
) )
func newServerRouter(context.Context, *iface.WGIface) (serverRouter, error) { type serverRouter struct {
return nil, fmt.Errorf("server route not supported on this os")
} }
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface) *serverRouter {
return &serverRouter{}
}
func (r *serverRouter) updateRoutes(routesMap map[string]*route.Route) error {
return nil
}
func (r *serverRouter) cleanUp() {}

View File

@@ -13,7 +13,7 @@ import (
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
type defaultServerRouter struct { type serverRouter struct {
mux sync.Mutex mux sync.Mutex
ctx context.Context ctx context.Context
routes map[string]*route.Route routes map[string]*route.Route
@@ -21,21 +21,16 @@ type defaultServerRouter struct {
wgInterface *iface.WGIface wgInterface *iface.WGIface
} }
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface) (serverRouter, error) { func newServerRouter(ctx context.Context, wgInterface *iface.WGIface) *serverRouter {
firewall, err := newFirewall(ctx) return &serverRouter{
if err != nil {
return nil, err
}
return &defaultServerRouter{
ctx: ctx, ctx: ctx,
routes: make(map[string]*route.Route), routes: make(map[string]*route.Route),
firewall: firewall, firewall: NewFirewall(ctx),
wgInterface: wgInterface, wgInterface: wgInterface,
}, nil }
} }
func (m *defaultServerRouter) updateRoutes(routesMap map[string]*route.Route) error { func (m *serverRouter) updateRoutes(routesMap map[string]*route.Route) error {
serverRoutesToRemove := make([]string, 0) serverRoutesToRemove := make([]string, 0)
if len(routesMap) > 0 { if len(routesMap) > 0 {
@@ -86,7 +81,7 @@ func (m *defaultServerRouter) updateRoutes(routesMap map[string]*route.Route) er
return nil return nil
} }
func (m *defaultServerRouter) removeFromServerNetwork(route *route.Route) error { func (m *serverRouter) removeFromServerNetwork(route *route.Route) error {
select { select {
case <-m.ctx.Done(): case <-m.ctx.Done():
log.Infof("not removing from server network because context is done") log.Infof("not removing from server network because context is done")
@@ -103,7 +98,7 @@ func (m *defaultServerRouter) removeFromServerNetwork(route *route.Route) error
} }
} }
func (m *defaultServerRouter) addToServerNetwork(route *route.Route) error { func (m *serverRouter) addToServerNetwork(route *route.Route) error {
select { select {
case <-m.ctx.Done(): case <-m.ctx.Done():
log.Infof("not adding to server network because context is done") log.Infof("not adding to server network because context is done")
@@ -120,6 +115,6 @@ func (m *defaultServerRouter) addToServerNetwork(route *route.Route) error {
} }
} }
func (m *defaultServerRouter) cleanUp() { func (m *serverRouter) cleanUp() {
m.firewall.CleanRoutingRules() m.firewall.CleanRoutingRules()
} }

View File

@@ -20,7 +20,7 @@ func InterfaceFilter(disallowList []string) func(string) bool {
for _, s := range disallowList { for _, s := range disallowList {
if strings.HasPrefix(iFace, s) { if strings.HasPrefix(iFace, s) {
log.Tracef("ignoring interface %s - it is not allowed", iFace) log.Debugf("ignoring interface %s - it is not allowed", iFace)
return false return false
} }
} }

View File

@@ -54,17 +54,14 @@ type bpfSpecs struct {
// //
// It can be passed ebpf.CollectionSpec.Assign. // It can be passed ebpf.CollectionSpec.Assign.
type bpfProgramSpecs struct { type bpfProgramSpecs struct {
NbXdpProg *ebpf.ProgramSpec `ebpf:"nb_xdp_prog"` XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"`
} }
// bpfMapSpecs contains maps before they are loaded into the kernel. // bpfMapSpecs contains maps before they are loaded into the kernel.
// //
// It can be passed ebpf.CollectionSpec.Assign. // It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct { type bpfMapSpecs struct {
NbFeatures *ebpf.MapSpec `ebpf:"nb_features"` XdpPortMap *ebpf.MapSpec `ebpf:"xdp_port_map"`
NbMapDnsIp *ebpf.MapSpec `ebpf:"nb_map_dns_ip"`
NbMapDnsPort *ebpf.MapSpec `ebpf:"nb_map_dns_port"`
NbWgProxySettingsMap *ebpf.MapSpec `ebpf:"nb_wg_proxy_settings_map"`
} }
// bpfObjects contains all objects after they have been loaded into the kernel. // bpfObjects contains all objects after they have been loaded into the kernel.
@@ -86,18 +83,12 @@ func (o *bpfObjects) Close() error {
// //
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfMaps struct { type bpfMaps struct {
NbFeatures *ebpf.Map `ebpf:"nb_features"` XdpPortMap *ebpf.Map `ebpf:"xdp_port_map"`
NbMapDnsIp *ebpf.Map `ebpf:"nb_map_dns_ip"`
NbMapDnsPort *ebpf.Map `ebpf:"nb_map_dns_port"`
NbWgProxySettingsMap *ebpf.Map `ebpf:"nb_wg_proxy_settings_map"`
} }
func (m *bpfMaps) Close() error { func (m *bpfMaps) Close() error {
return _BpfClose( return _BpfClose(
m.NbFeatures, m.XdpPortMap,
m.NbMapDnsIp,
m.NbMapDnsPort,
m.NbWgProxySettingsMap,
) )
} }
@@ -105,12 +96,12 @@ func (m *bpfMaps) Close() error {
// //
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct { type bpfPrograms struct {
NbXdpProg *ebpf.Program `ebpf:"nb_xdp_prog"` XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"`
} }
func (p *bpfPrograms) Close() error { func (p *bpfPrograms) Close() error {
return _BpfClose( return _BpfClose(
p.NbXdpProg, p.XdpProgFunc,
) )
} }

Binary file not shown.

View File

@@ -54,17 +54,14 @@ type bpfSpecs struct {
// //
// It can be passed ebpf.CollectionSpec.Assign. // It can be passed ebpf.CollectionSpec.Assign.
type bpfProgramSpecs struct { type bpfProgramSpecs struct {
NbXdpProg *ebpf.ProgramSpec `ebpf:"nb_xdp_prog"` XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"`
} }
// bpfMapSpecs contains maps before they are loaded into the kernel. // bpfMapSpecs contains maps before they are loaded into the kernel.
// //
// It can be passed ebpf.CollectionSpec.Assign. // It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct { type bpfMapSpecs struct {
NbFeatures *ebpf.MapSpec `ebpf:"nb_features"` XdpPortMap *ebpf.MapSpec `ebpf:"xdp_port_map"`
NbMapDnsIp *ebpf.MapSpec `ebpf:"nb_map_dns_ip"`
NbMapDnsPort *ebpf.MapSpec `ebpf:"nb_map_dns_port"`
NbWgProxySettingsMap *ebpf.MapSpec `ebpf:"nb_wg_proxy_settings_map"`
} }
// bpfObjects contains all objects after they have been loaded into the kernel. // bpfObjects contains all objects after they have been loaded into the kernel.
@@ -86,18 +83,12 @@ func (o *bpfObjects) Close() error {
// //
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfMaps struct { type bpfMaps struct {
NbFeatures *ebpf.Map `ebpf:"nb_features"` XdpPortMap *ebpf.Map `ebpf:"xdp_port_map"`
NbMapDnsIp *ebpf.Map `ebpf:"nb_map_dns_ip"`
NbMapDnsPort *ebpf.Map `ebpf:"nb_map_dns_port"`
NbWgProxySettingsMap *ebpf.Map `ebpf:"nb_wg_proxy_settings_map"`
} }
func (m *bpfMaps) Close() error { func (m *bpfMaps) Close() error {
return _BpfClose( return _BpfClose(
m.NbFeatures, m.XdpPortMap,
m.NbMapDnsIp,
m.NbMapDnsPort,
m.NbWgProxySettingsMap,
) )
} }
@@ -105,12 +96,12 @@ func (m *bpfMaps) Close() error {
// //
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct { type bpfPrograms struct {
NbXdpProg *ebpf.Program `ebpf:"nb_xdp_prog"` XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"`
} }
func (p *bpfPrograms) Close() error { func (p *bpfPrograms) Close() error {
return _BpfClose( return _BpfClose(
p.NbXdpProg, p.XdpProgFunc,
) )
} }

Binary file not shown.

View File

@@ -0,0 +1,84 @@
//go:build linux && !android
package ebpf
import (
_ "embed"
"net"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/rlimit"
)
const (
mapKeyProxyPort uint32 = 0
mapKeyWgPort uint32 = 1
)
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang-14 bpf src/portreplace.c --
// EBPF is a wrapper for eBPF program
type EBPF struct {
link link.Link
}
// NewEBPF create new EBPF instance
func NewEBPF() *EBPF {
return &EBPF{}
}
// Load load ebpf program
func (l *EBPF) Load(proxyPort, wgPort int) error {
// it required for Docker
err := rlimit.RemoveMemlock()
if err != nil {
return err
}
ifce, err := net.InterfaceByName("lo")
if err != nil {
return err
}
// Load pre-compiled programs into the kernel.
objs := bpfObjects{}
err = loadBpfObjects(&objs, nil)
if err != nil {
return err
}
defer func() {
_ = objs.Close()
}()
err = objs.XdpPortMap.Put(mapKeyProxyPort, uint16(proxyPort))
if err != nil {
return err
}
err = objs.XdpPortMap.Put(mapKeyWgPort, uint16(wgPort))
if err != nil {
return err
}
defer func() {
_ = objs.XdpPortMap.Close()
}()
l.link, err = link.AttachXDP(link.XDPOptions{
Program: objs.XdpProgFunc,
Interface: ifce.Index,
})
if err != nil {
return err
}
return err
}
// Free free ebpf program
func (l *EBPF) Free() error {
if l.link != nil {
return l.link.Close()
}
return nil
}

View File

@@ -0,0 +1,18 @@
//go:build linux
package ebpf
import (
"testing"
)
func Test_newEBPF(t *testing.T) {
ebpf := NewEBPF()
err := ebpf.Load(1234, 51892)
defer func() {
_ = ebpf.Free()
}()
if err != nil {
t.Errorf("%s", err)
}
}

View File

@@ -0,0 +1,90 @@
#include <stdbool.h>
#include <linux/if_ether.h> // ETH_P_IP
#include <linux/udp.h>
#include <linux/ip.h>
#include <netinet/in.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#define bpf_printk(fmt, ...) \
({ \
char ____fmt[] = fmt; \
bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
})
const __u32 map_key_proxy_port = 0;
const __u32 map_key_wg_port = 1;
struct bpf_map_def SEC("maps") xdp_port_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(__u16),
.max_entries = 10,
};
__u16 proxy_port = 0;
__u16 wg_port = 0;
bool read_port_settings() {
__u16 *value;
value = bpf_map_lookup_elem(&xdp_port_map, &map_key_proxy_port);
if(!value) {
return false;
}
proxy_port = *value;
value = bpf_map_lookup_elem(&xdp_port_map, &map_key_wg_port);
if(!value) {
return false;
}
wg_port = *value;
return true;
}
SEC("xdp")
int xdp_prog_func(struct xdp_md *ctx) {
if(proxy_port == 0 || wg_port == 0) {
if(!read_port_settings()){
return XDP_PASS;
}
bpf_printk("proxy port: %d, wg port: %d", proxy_port, wg_port);
}
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
struct iphdr *ip = (data + sizeof(struct ethhdr));
struct udphdr *udp = (data + sizeof(struct ethhdr) + sizeof(struct iphdr));
// return early if not enough data
if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) > data_end){
return XDP_PASS;
}
// skip non IPv4 packages
if (eth->h_proto != htons(ETH_P_IP)) {
return XDP_PASS;
}
if (ip->protocol != IPPROTO_UDP) {
return XDP_PASS;
}
// 2130706433 = 127.0.0.1
if (ip->daddr != htonl(2130706433)) {
return XDP_PASS;
}
if (udp->source != htons(wg_port)){
return XDP_PASS;
}
__be16 new_src_port = udp->dest;
__be16 new_dst_port = htons(proxy_port);
udp->dest = new_dst_port;
udp->source = new_src_port;
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";

View File

@@ -14,7 +14,7 @@ func (w *Factory) GetProxy() Proxy {
func (w *Factory) Free() error { func (w *Factory) Free() error {
if w.ebpfProxy != nil { if w.ebpfProxy != nil {
return w.ebpfProxy.Free() return w.ebpfProxy.CloseConn()
} }
return nil return nil
} }

View File

@@ -12,7 +12,7 @@ func NewFactory(wgPort int) *Factory {
ebpfProxy := NewWGEBPFProxy(wgPort) ebpfProxy := NewWGEBPFProxy(wgPort)
err := ebpfProxy.Listen() err := ebpfProxy.Listen()
if err != nil { if err != nil {
log.Warnf("failed to initialize ebpf proxy, fallback to user space proxy: %s", err) log.Errorf("failed to initialize ebpf proxy: %s", err)
return f return f
} }

View File

@@ -12,15 +12,15 @@ import (
"github.com/google/gopacket" "github.com/google/gopacket"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/internal/ebpf" ebpf2 "github.com/netbirdio/netbird/client/internal/wgproxy/ebpf"
ebpfMgr "github.com/netbirdio/netbird/client/internal/ebpf/manager"
) )
// WGEBPFProxy definition for proxy with EBPF support // WGEBPFProxy definition for proxy with EBPF support
type WGEBPFProxy struct { type WGEBPFProxy struct {
ebpfManager ebpfMgr.Manager ebpf *ebpf2.EBPF
lastUsedPort uint16 lastUsedPort uint16
localWGListenPort int localWGListenPort int
@@ -36,7 +36,7 @@ func NewWGEBPFProxy(wgPort int) *WGEBPFProxy {
log.Debugf("instantiate ebpf proxy") log.Debugf("instantiate ebpf proxy")
wgProxy := &WGEBPFProxy{ wgProxy := &WGEBPFProxy{
localWGListenPort: wgPort, localWGListenPort: wgPort,
ebpfManager: ebpf.GetEbpfManagerInstance(), ebpf: ebpf2.NewEBPF(),
lastUsedPort: 0, lastUsedPort: 0,
turnConnStore: make(map[uint16]net.Conn), turnConnStore: make(map[uint16]net.Conn),
} }
@@ -56,7 +56,7 @@ func (p *WGEBPFProxy) Listen() error {
return err return err
} }
err = p.ebpfManager.LoadWgProxy(wgPorxyPort, p.localWGListenPort) err = p.ebpf.Load(wgPorxyPort, p.localWGListenPort)
if err != nil { if err != nil {
return err return err
} }
@@ -69,7 +69,7 @@ func (p *WGEBPFProxy) Listen() error {
p.conn, err = net.ListenUDP("udp", &addr) p.conn, err = net.ListenUDP("udp", &addr)
if err != nil { if err != nil {
cErr := p.Free() cErr := p.Free()
if cErr != nil { if err != nil {
log.Errorf("failed to close the wgproxy: %s", cErr) log.Errorf("failed to close the wgproxy: %s", cErr)
} }
return err return err
@@ -104,13 +104,12 @@ func (p *WGEBPFProxy) CloseConn() error {
// Free resources // Free resources
func (p *WGEBPFProxy) Free() error { func (p *WGEBPFProxy) Free() error {
log.Debugf("free up ebpf wg proxy")
var err1, err2, err3 error var err1, err2, err3 error
if p.conn != nil { if p.conn != nil {
err1 = p.conn.Close() err1 = p.conn.Close()
} }
err2 = p.ebpfManager.FreeWGProxy() err2 = p.ebpf.Free()
if p.rawConn != nil { if p.rawConn != nil {
err3 = p.rawConn.Close() err3 = p.rawConn.Close()
} }
@@ -135,7 +134,6 @@ func (p *WGEBPFProxy) proxyToLocal(endpointPort uint16, remoteConn net.Conn) {
log.Errorf("failed to read from turn conn (endpoint: :%d): %s", endpointPort, err) log.Errorf("failed to read from turn conn (endpoint: :%d): %s", endpointPort, err)
} }
p.removeTurnConn(endpointPort) p.removeTurnConn(endpointPort)
log.Infof("stop forward turn packages to port: %d. error: %s", endpointPort, err)
return return
} }
err = p.sendPkg(buf[:n], endpointPort) err = p.sendPkg(buf[:n], endpointPort)
@@ -155,11 +153,9 @@ func (p *WGEBPFProxy) proxyToRemote() {
return return
} }
p.turnConnMutex.Lock()
conn, ok := p.turnConnStore[uint16(addr.Port)] conn, ok := p.turnConnStore[uint16(addr.Port)]
p.turnConnMutex.Unlock()
if !ok { if !ok {
log.Infof("turn conn not found by port: %d", addr.Port) log.Errorf("turn conn not found by port: %d", addr.Port)
continue continue
} }

View File

@@ -20,7 +20,6 @@ type WGUserSpaceProxy struct {
// NewWGUserSpaceProxy instantiate a user space WireGuard proxy // NewWGUserSpaceProxy instantiate a user space WireGuard proxy
func NewWGUserSpaceProxy(wgPort int) *WGUserSpaceProxy { func NewWGUserSpaceProxy(wgPort int) *WGUserSpaceProxy {
log.Debugf("instantiate new userspace proxy")
p := &WGUserSpaceProxy{ p := &WGUserSpaceProxy{
localWGListenPort: wgPort, localWGListenPort: wgPort,
} }

View File

@@ -1,77 +0,0 @@
<Wix
xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package Name="NetBird" Version="$(env.NETBIRD_VERSION)" Manufacturer="Wiretrustee UG (haftungsbeschreankt)" Language="1033" UpgradeCode="6456ec4e-3ad6-4b9b-a2be-98e81cb21ccf"
InstallerVersion="500" Compressed="yes" Codepage="utf-8" >
<MediaTemplate EmbedCab="yes" />
<Feature Id="NetbirdFeature" Title="Netbird" Level="1">
<ComponentGroupRef Id="NetbirdFilesComponent" />
</Feature>
<MajorUpgrade AllowSameVersionUpgrades='yes' DowngradeErrorMessage="A newer version of [ProductName] is already installed. Setup will now exit."/>
<StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="NetbirdInstallDir" Name="Netbird">
<Component Id="NetbirdFiles" Guid="db3165de-cc6e-4922-8396-9d892950e23e" Bitness="always64">
<File ProcessorArchitecture="x64" Source=".\dist\netbird_windows_amd64\netbird.exe" KeyPath="yes" />
<File ProcessorArchitecture="x64" Source=".\dist\netbird_windows_amd64\netbird-ui.exe">
<Shortcut Id="NetbirdDesktopShortcut" Directory="DesktopFolder" Name="NetBird" WorkingDirectory="NetbirdInstallDir" Icon="NetbirdIcon" />
<Shortcut Id="NetbirdStartMenuShortcut" Directory="StartMenuFolder" Name="NetBird" WorkingDirectory="NetbirdInstallDir" Icon="NetbirdIcon" />
</File>
<File ProcessorArchitecture="x64" Source=".\dist\netbird_windows_amd64\wintun.dll" />
<ServiceInstall
Id="NetBirdService"
Name="NetBird"
DisplayName="NetBird"
Description="A WireGuard-based mesh network that connects your devices into a single private network."
Start="auto" Type="ownProcess"
ErrorControl="normal"
Account="LocalSystem"
Vital="yes"
Interactive="no"
Arguments='service run config [CommonAppDataFolder]Netbird\config.json log-level info'
/>
<ServiceControl Id="NetBirdService" Name="NetBird" Start="install" Stop="both" Remove="uninstall" Wait="yes" />
<Environment Id="UpdatePath" Name="PATH" Value="[NetbirdInstallDir]" Part="last" Action="set" System="yes" />
</Component>
</Directory>
</StandardDirectory>
<ComponentGroup Id="NetbirdFilesComponent">
<ComponentRef Id="NetbirdFiles" />
</ComponentGroup>
<Property Id="cmd" Value="cmd.exe"/>
<CustomAction Id="KillDaemon"
ExeCommand='/c "taskkill /im netbird.exe"'
Execute="deferred"
Property="cmd"
Impersonate="no"
Return="ignore"
/>
<CustomAction Id="KillUI"
ExeCommand='/c "taskkill /im netbird-ui.exe"'
Execute="deferred"
Property="cmd"
Impersonate="no"
Return="ignore"
/>
<InstallExecuteSequence>
<!-- For Uninstallation -->
<Custom Action="KillDaemon" Before="RemoveFiles" Condition="Installed"/>
<Custom Action="KillUI" After="KillDaemon" Condition="Installed"/>
</InstallExecuteSequence>
<!-- Icons -->
<Icon Id="NetbirdIcon" SourceFile=".\client\ui\netbird.ico" />
<Property Id="ARPPRODUCTICON" Value="NetbirdIcon" />
</Package>
</Wix>

View File

@@ -3,11 +3,10 @@ package server
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/netbirdio/netbird/client/internal/auth"
"sync" "sync"
"time" "time"
"github.com/netbirdio/netbird/client/internal/auth"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
@@ -316,7 +315,7 @@ func (s *Server) WaitSSOLogin(callerCtx context.Context, msg *proto.WaitSSOLogin
tokenInfo, err := s.oauthAuthFlow.flow.WaitToken(waitCTX, flowInfo) tokenInfo, err := s.oauthAuthFlow.flow.WaitToken(waitCTX, flowInfo)
if err != nil { if err != nil {
if err == context.Canceled { if err == context.Canceled {
return nil, nil //nolint:nilnil return nil, nil
} }
s.mutex.Lock() s.mutex.Lock()
s.oauthAuthFlow.expiresAt = time.Now() s.oauthAuthFlow.expiresAt = time.Now()

View File

@@ -130,22 +130,16 @@ func ParseNameServerURL(nsURL string) (NameServer, error) {
// Copy copies a nameserver group object // Copy copies a nameserver group object
func (g *NameServerGroup) Copy() *NameServerGroup { func (g *NameServerGroup) Copy() *NameServerGroup {
nsGroup := &NameServerGroup{ return &NameServerGroup{
ID: g.ID, ID: g.ID,
Name: g.Name, Name: g.Name,
Description: g.Description, Description: g.Description,
NameServers: make([]NameServer, len(g.NameServers)), NameServers: g.NameServers,
Groups: make([]string, len(g.Groups)), Groups: g.Groups,
Enabled: g.Enabled, Enabled: g.Enabled,
Primary: g.Primary, Primary: g.Primary,
Domains: make([]string, len(g.Domains)), Domains: g.Domains,
} }
copy(nsGroup.NameServers, g.NameServers)
copy(nsGroup.Groups, g.Groups)
copy(nsGroup.Domains, g.Domains)
return nsGroup
} }
// IsEqual compares one nameserver group with the other // IsEqual compares one nameserver group with the other

2
go.mod
View File

@@ -31,7 +31,7 @@ require (
fyne.io/fyne/v2 v2.1.4 fyne.io/fyne/v2 v2.1.4
github.com/c-robinson/iplib v1.0.3 github.com/c-robinson/iplib v1.0.3
github.com/cilium/ebpf v0.10.0 github.com/cilium/ebpf v0.10.0
github.com/coreos/go-iptables v0.7.0 github.com/coreos/go-iptables v0.6.0
github.com/creack/pty v1.1.18 github.com/creack/pty v1.1.18
github.com/eko/gocache/v3 v3.1.1 github.com/eko/gocache/v3 v3.1.1
github.com/getlantern/systray v1.2.1 github.com/getlantern/systray v1.2.1

4
go.sum
View File

@@ -131,8 +131,8 @@ github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcD
github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U= github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=

View File

@@ -112,7 +112,7 @@ func (w *WGIface) Close() error {
return w.tun.Close() return w.tun.Close()
} }
// SetFilter sets packet filters for the userspace implementation // SetFilter sets packet filters for the userspace impelemntation
func (w *WGIface) SetFilter(filter PacketFilter) error { func (w *WGIface) SetFilter(filter PacketFilter) error {
w.mu.Lock() w.mu.Lock()
defer w.mu.Unlock() defer w.mu.Unlock()

View File

@@ -161,7 +161,7 @@ func getModulePath(name string) (string, error) {
} }
if err != nil { if err != nil {
// skip broken files // skip broken files
return nil //nolint:nilerr return nil
} }
if !info.Type().IsRegular() { if !info.Type().IsRegular() {

View File

@@ -146,6 +146,9 @@ func (c *wGConfigurer) removeAllowedIP(peerKey string, allowedIP string) error {
} }
} }
if err != nil {
return err
}
peer := wgtypes.PeerConfig{ peer := wgtypes.PeerConfig{
PublicKey: peerKeyParsed, PublicKey: peerKeyParsed,
UpdateOnly: true, UpdateOnly: true,

View File

@@ -168,4 +168,4 @@ env | grep NETBIRD
envsubst <docker-compose.yml.tmpl >docker-compose.yml envsubst <docker-compose.yml.tmpl >docker-compose.yml
envsubst <management.json.tmpl >management.json envsubst <management.json.tmpl >management.json
envsubst <turnserver.conf.tmpl >turnserver.conf envsubst <turnserver.conf.tmpl >turnserver.conf

View File

@@ -36,7 +36,7 @@ services:
volumes: volumes:
- $SIGNAL_VOLUMENAME:/var/lib/netbird - $SIGNAL_VOLUMENAME:/var/lib/netbird
ports: ports:
- $NETBIRD_SIGNAL_PORT:80 - 10000:80
# # port and command for Let's Encrypt validation # # port and command for Let's Encrypt validation
# - 443:443 # - 443:443
# command: ["--letsencrypt-domain", "$NETBIRD_LETSENCRYPT_DOMAIN", "--log-file", "console"] # command: ["--letsencrypt-domain", "$NETBIRD_LETSENCRYPT_DOMAIN", "--log-file", "console"]

View File

@@ -47,10 +47,10 @@ check_jq() {
fi fi
} }
wait_crdb() { wait_pgdb() {
set +e set +e
while true; do while true; do
if $DOCKER_COMPOSE_COMMAND exec -T crdb curl -sf -o /dev/null 'http://localhost:8080/health?ready=1'; then if $DOCKER_COMPOSE_COMMAND exec -T pgdb pg_isready -U postgres; then
break break
fi fi
echo -n " ." echo -n " ."
@@ -60,15 +60,15 @@ wait_crdb() {
set -e set -e
} }
init_crdb() { init_pgdb() {
echo -e "\nInitializing Zitadel's CockroachDB\n\n" echo -e "\nInitializing Zitadel's CockroachDB\n\n"
$DOCKER_COMPOSE_COMMAND up -d crdb $DOCKER_COMPOSE_COMMAND up -d pgdb
echo "" echo ""
# shellcheck disable=SC2028 # shellcheck disable=SC2028
echo -n "Waiting cockroachDB to become ready " echo -n "Waiting cockroachDB to become ready "
wait_crdb wait_pgdb
$DOCKER_COMPOSE_COMMAND exec -T crdb /bin/bash -c "cp /cockroach/certs/* /zitadel-certs/ && cockroach cert create-client --overwrite --certs-dir /zitadel-certs/ --ca-key /zitadel-certs/ca.key zitadel_user && chown -R 1000:1000 /zitadel-certs/" #$DOCKER_COMPOSE_COMMAND exec -T pgdb /bin/bash -c "cp /cockroach/certs/* /zitadel-certs/ && cockroach cert create-client --overwrite --certs-dir /zitadel-certs/ --ca-key /zitadel-certs/ca.key zitadel_user && chown -R 1000:1000 /zitadel-certs/"
handle_request_command_status $? "init_crdb failed" "" handle_request_command_status $? "init_pgdb failed" ""
} }
get_main_ip_address() { get_main_ip_address() {
@@ -135,8 +135,7 @@ create_new_application() {
APPLICATION_NAME=$3 APPLICATION_NAME=$3
BASE_REDIRECT_URL1=$4 BASE_REDIRECT_URL1=$4
BASE_REDIRECT_URL2=$5 BASE_REDIRECT_URL2=$5
LOGOUT_URL=$6 ZITADEL_DEV_MODE=$6
ZITADEL_DEV_MODE=$7
RESPONSE=$( RESPONSE=$(
curl -sS -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \ curl -sS -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \
@@ -149,7 +148,7 @@ create_new_application() {
"'"$BASE_REDIRECT_URL2"'" "'"$BASE_REDIRECT_URL2"'"
], ],
"postLogoutRedirectUris": [ "postLogoutRedirectUris": [
"'"$LOGOUT_URL"'" "'"$BASE_REDIRECT_URL1"'"
], ],
"RESPONSETypes": [ "RESPONSETypes": [
"OIDC_RESPONSE_TYPE_CODE" "OIDC_RESPONSE_TYPE_CODE"
@@ -340,10 +339,10 @@ init_zitadel() {
# create zitadel spa applications # create zitadel spa applications
echo "Creating new Zitadel SPA Dashboard application" echo "Creating new Zitadel SPA Dashboard application"
DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "$BASE_REDIRECT_URL/nb-auth" "$BASE_REDIRECT_URL/nb-silent-auth" "$BASE_REDIRECT_URL/" "$ZITADEL_DEV_MODE") DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "$BASE_REDIRECT_URL/nb-auth" "$BASE_REDIRECT_URL/nb-silent-auth" "$ZITADEL_DEV_MODE")
echo "Creating new Zitadel SPA Cli application" echo "Creating new Zitadel SPA Cli application"
CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "http://localhost:53000/" "true") CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "true")
MACHINE_USER_ID=$(create_service_user "$INSTANCE_URL" "$PAT") MACHINE_USER_ID=$(create_service_user "$INSTANCE_URL" "$PAT")
@@ -378,35 +377,12 @@ init_zitadel() {
export ZITADEL_ADMIN_PASSWORD export ZITADEL_ADMIN_PASSWORD
} }
check_nb_domain() {
DOMAIN=$1
if [ "$DOMAIN-x" == "-x" ]; then
echo "The NETBIRD_DOMAIN variable cannot be empty." > /dev/stderr
return 1
fi
if [ "$DOMAIN" == "netbird.example.com" ]; then
echo "The NETBIRD_DOMAIN cannot be netbird.example.com" > /dev/stderr
retrun 1
fi
return 0
}
read_nb_domain() {
READ_NETBIRD_DOMAIN=""
echo -n "Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): " > /dev/stderr
read -r READ_NETBIRD_DOMAIN < /dev/tty
if ! check_nb_domain "$READ_NETBIRD_DOMAIN"; then
read_nb_domain
fi
echo "$READ_NETBIRD_DOMAIN"
}
initEnvironment() { initEnvironment() {
CADDY_SECURE_DOMAIN="" CADDY_SECURE_DOMAIN=""
ZITADEL_EXTERNALSECURE="false" ZITADEL_EXTERNALSECURE="false"
ZITADEL_TLS_MODE="disabled" ZITADEL_TLS_MODE="disabled"
ZITADEL_MASTERKEY="$(openssl rand -base64 32 | head -c 32)" ZITADEL_MASTERKEY="$(openssl rand -base64 32 | head -c 32)"
USING_DOMAIN="true"
NETBIRD_PORT=80 NETBIRD_PORT=80
NETBIRD_HTTP_PROTOCOL="http" NETBIRD_HTTP_PROTOCOL="http"
TURN_USER="self" TURN_USER="self"
@@ -414,13 +390,18 @@ initEnvironment() {
TURN_MIN_PORT=49152 TURN_MIN_PORT=49152
TURN_MAX_PORT=65535 TURN_MAX_PORT=65535
if ! check_nb_domain "$NETBIRD_DOMAIN"; then NETBIRD_DOMAIN=$NETBIRD_DOMAIN
NETBIRD_DOMAIN=$(read_nb_domain) if [ "$NETBIRD_DOMAIN-x" == "-x" ] ; then
echo "NETBIRD_DOMAIN is not set, using the main IP address"
NETBIRD_DOMAIN=$(get_main_ip_address)
USING_DOMAIN="false"
fi fi
if [ "$NETBIRD_DOMAIN" == "use-ip" ]; then if [ "$NETBIRD_DOMAIN" == "localhost" ]; then
NETBIRD_DOMAIN=$(get_main_ip_address) USING_DOMAIN="false"
else fi
if [ $USING_DOMAIN == "true" ]; then
ZITADEL_EXTERNALSECURE="true" ZITADEL_EXTERNALSECURE="true"
ZITADEL_TLS_MODE="external" ZITADEL_TLS_MODE="external"
NETBIRD_PORT=443 NETBIRD_PORT=443
@@ -458,7 +439,7 @@ initEnvironment() {
mkdir -p machinekey mkdir -p machinekey
chmod 777 machinekey chmod 777 machinekey
init_crdb init_pgdb
echo -e "\nStarting Zidatel IDP for user management\n\n" echo -e "\nStarting Zidatel IDP for user management\n\n"
$DOCKER_COMPOSE_COMMAND up -d caddy zitadel $DOCKER_COMPOSE_COMMAND up -d caddy zitadel
@@ -487,48 +468,7 @@ renderCaddyfile() {
} }
} }
(security_headers) {
header * {
# enable HSTS
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#strict-transport-security-hsts
# NOTE: Read carefully how this header works before using it.
# If the HSTS header is misconfigured or if there is a problem with
# the SSL/TLS certificate being used, legitimate users might be unable
# to access the website. For example, if the HSTS header is set to a
# very long duration and the SSL/TLS certificate expires or is revoked,
# legitimate users might be unable to access the website until
# the HSTS header duration has expired.
# The recommended value for the max-age is 2 year (63072000 seconds).
# But we are using 1 hour (3600 seconds) for testing purposes
# and ensure that the website is working properly before setting
# to two years.
Strict-Transport-Security "max-age=3600; includeSubDomains; preload"
# disable clients from sniffing the media type
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-content-type-options
X-Content-Type-Options "nosniff"
# clickjacking protection
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-frame-options
X-Frame-Options "DENY"
# xss protection
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-xss-protection
X-XSS-Protection "1; mode=block"
# Remove -Server header, which is an information leak
# Remove Caddy from Headers
-Server
# keep referrer data off of HTTP connections
# https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#referrer-policy
Referrer-Policy strict-origin-when-cross-origin
}
}
:80${CADDY_SECURE_DOMAIN} { :80${CADDY_SECURE_DOMAIN} {
import security_headers
# Signal # Signal
reverse_proxy /signalexchange.SignalExchange/* h2c://signal:10000 reverse_proxy /signalexchange.SignalExchange/* h2c://signal:10000
# Management # Management
@@ -654,16 +594,25 @@ renderZitadelEnv() {
cat <<EOF cat <<EOF
ZITADEL_LOG_LEVEL=debug ZITADEL_LOG_LEVEL=debug
ZITADEL_MASTERKEY=$ZITADEL_MASTERKEY ZITADEL_MASTERKEY=$ZITADEL_MASTERKEY
ZITADEL_DATABASE_COCKROACH_HOST=crdb #ZITADEL_DATABASE_COCKROACH_HOST=pgdb
ZITADEL_DATABASE_COCKROACH_USER_USERNAME=zitadel_user #ZITADEL_DATABASE_COCKROACH_USER_USERNAME=zitadel_user
ZITADEL_DATABASE_COCKROACH_USER_SSL_MODE=verify-full #ZITADEL_DATABASE_COCKROACH_USER_SSL_MODE=verify-full
ZITADEL_DATABASE_COCKROACH_USER_SSL_ROOTCERT="/crdb-certs/ca.crt" #ZITADEL_DATABASE_COCKROACH_USER_SSL_ROOTCERT="/pgdb-certs/ca.crt"
ZITADEL_DATABASE_COCKROACH_USER_SSL_CERT="/crdb-certs/client.zitadel_user.crt" #ZITADEL_DATABASE_COCKROACH_USER_SSL_CERT="/pgdb-certs/client.zitadel_user.crt"
ZITADEL_DATABASE_COCKROACH_USER_SSL_KEY="/crdb-certs/client.zitadel_user.key" #ZITADEL_DATABASE_COCKROACH_USER_SSL_KEY="/pgdb-certs/client.zitadel_user.key"
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_MODE=verify-full #ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_MODE=verify-full
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_ROOTCERT="/crdb-certs/ca.crt" #ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_ROOTCERT="/pgdb-certs/ca.crt"
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_CERT="/crdb-certs/client.root.crt" #ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_CERT="/pgdb-certs/client.root.crt"
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_KEY="/crdb-certs/client.root.key" #ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_KEY="/pgdb-certs/client.root.key"
ZITADEL_DATABASE_POSTGRES_HOST=pgdb
ZITADEL_DATABASE_POSTGRES_PORT=5432
ZITADEL_DATABASE_POSTGRES_DATABASE=zitadeldb
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME=zitadeladmin
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD=zitadeladmin
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE=disable
ZITADEL_DATABASE_POSTGRES_USER_USERNAME=zitadeluser
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD=zitadeluser
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE=disable
ZITADEL_EXTERNALSECURE=$ZITADEL_EXTERNALSECURE ZITADEL_EXTERNALSECURE=$ZITADEL_EXTERNALSECURE
ZITADEL_TLS_ENABLED="false" ZITADEL_TLS_ENABLED="false"
ZITADEL_EXTERNALPORT=$NETBIRD_PORT ZITADEL_EXTERNALPORT=$NETBIRD_PORT
@@ -715,11 +664,10 @@ services:
command: [ command: [
"--port", "80", "--port", "80",
"--log-file", "console", "--log-file", "console",
"--log-level", "info", "--log-level", "debug",
"--disable-anonymous-metrics=false", "--disable-anonymous-metrics=false",
"--single-account-mode-domain=netbird.selfhosted", "--single-account-mode-domain=netbird.selfhosted",
"--dns-domain=netbird.selfhosted", "--dns-domain=netbird.selfhosted",
"--idp-sign-key-refresh-enabled",
] ]
# Coturn, AKA relay server # Coturn, AKA relay server
coturn: coturn:
@@ -740,23 +688,33 @@ services:
env_file: env_file:
- ./zitadel.env - ./zitadel.env
depends_on: depends_on:
crdb: pgdb:
condition: 'service_healthy' condition: 'service_healthy'
volumes: volumes:
- ./machinekey:/machinekey - ./machinekey:/machinekey
- netbird_zitadel_certs:/crdb-certs:ro - netbird_zitadel_certs:/pgdb-certs:ro
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8080/debug/healthz" ]
interval: '10s'
timeout: '30s'
retries: 5
start_period: '20s'
# CockroachDB for zitadel # CockroachDB for zitadel
crdb: pgdb:
restart: 'always' restart: 'always'
networks: [netbird] networks: [netbird]
image: 'cockroachdb/cockroach:v22.2.2' image: 'postgres:15'
command: 'start-single-node --advertise-addr crdb' environment:
- POSTGRES_USER=zitadeladmin
- POSTGRES_PASSWORD=zitadeladmin
- POSTGRES_DB=zitadeldb
#command: 'start-single-node --advertise-addr pgdb'
volumes: volumes:
- netbird_crdb_data:/cockroach/cockroach-data - netbird_pgdb_data:/cockroach/cockroach-data
- netbird_crdb_certs:/cockroach/certs - netbird_pgdb_certs:/cockroach/certs
- netbird_zitadel_certs:/zitadel-certs - netbird_zitadel_certs:/zitadel-certs
healthcheck: healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8080/health?ready=1" ] test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: '10s' interval: '10s'
timeout: '30s' timeout: '30s'
retries: 5 retries: 5
@@ -765,8 +723,8 @@ services:
volumes: volumes:
netbird_management: netbird_management:
netbird_caddy_data: netbird_caddy_data:
netbird_crdb_data: netbird_pgdb_data:
netbird_crdb_certs: netbird_pgdb_certs:
netbird_zitadel_certs: netbird_zitadel_certs:
networks: networks:

View File

@@ -21,5 +21,4 @@ NETBIRD_AUTH_USER_ID_CLAIM="email"
NETBIRD_AUTH_DEVICE_AUTH_SCOPE="openid email" NETBIRD_AUTH_DEVICE_AUTH_SCOPE="openid email"
NETBIRD_MGMT_IDP=$CI_NETBIRD_MGMT_IDP NETBIRD_MGMT_IDP=$CI_NETBIRD_MGMT_IDP
NETBIRD_IDP_MGMT_CLIENT_ID=$CI_NETBIRD_IDP_MGMT_CLIENT_ID NETBIRD_IDP_MGMT_CLIENT_ID=$CI_NETBIRD_IDP_MGMT_CLIENT_ID
NETBIRD_IDP_MGMT_CLIENT_SECRET=$CI_NETBIRD_IDP_MGMT_CLIENT_SECRET NETBIRD_IDP_MGMT_CLIENT_SECRET=$CI_NETBIRD_IDP_MGMT_CLIENT_SECRET
NETBIRD_SIGNAL_PORT=12345

View File

@@ -61,12 +61,12 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
peersUpdateManager := mgmt.NewPeersUpdateManager() peersUpdateManager := mgmt.NewPeersUpdateManager()
eventStore := &activity.InMemoryEventStore{} eventStore := &activity.InMemoryEventStore{}
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
eventStore, false) eventStore)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig) turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager, nil, nil) mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -19,26 +19,28 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/miekg/dns" "github.com/miekg/dns"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/http2/h2c" "golang.org/x/net/http2/h2c"
"github.com/netbirdio/netbird/management/server/activity/sqlite"
httpapi "github.com/netbirdio/netbird/management/server/http"
"github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/netbirdio/netbird/management/server/metrics"
"github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/util"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
"github.com/netbirdio/netbird/encryption" "github.com/netbirdio/netbird/encryption"
mgmtProto "github.com/netbirdio/netbird/management/proto" mgmtProto "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/activity/sqlite"
httpapi "github.com/netbirdio/netbird/management/server/http"
"github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/netbirdio/netbird/management/server/metrics"
"github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/util"
) )
// ManagementLegacyPort is the port that was used before by the Management gRPC server. // ManagementLegacyPort is the port that was used before by the Management gRPC server.
@@ -70,15 +72,10 @@ var (
Use: "management", Use: "management",
Short: "start NetBird Management Server", Short: "start NetBird Management Server",
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(cmd *cobra.Command, args []string) error {
flag.Parse()
err := util.InitLog(logLevel, logFile)
if err != nil {
return fmt.Errorf("failed initializing log %v", err)
}
// detect whether user specified a port // detect whether user specified a port
userPort := cmd.Flag("port").Changed userPort := cmd.Flag("port").Changed
var err error
config, err = loadMgmtConfig(mgmtConfig) config, err = loadMgmtConfig(mgmtConfig)
if err != nil { if err != nil {
return fmt.Errorf("failed reading provided config file: %s: %v", mgmtConfig, err) return fmt.Errorf("failed reading provided config file: %s: %v", mgmtConfig, err)
@@ -107,7 +104,13 @@ var (
return nil return nil
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := handleRebrand(cmd) flag.Parse()
err := util.InitLog(logLevel, logFile)
if err != nil {
return fmt.Errorf("failed initializing log %v", err)
}
err = handleRebrand(cmd)
if err != nil { if err != nil {
return fmt.Errorf("failed to migrate files %v", err) return fmt.Errorf("failed to migrate files %v", err)
} }
@@ -143,22 +146,12 @@ var (
if disableSingleAccMode { if disableSingleAccMode {
mgmtSingleAccModeDomain = "" mgmtSingleAccModeDomain = ""
} }
eventStore, key, err := initEventStore(config.Datadir, config.DataStoreEncryptionKey) eventStore, err := sqlite.NewSQLiteStore(config.Datadir)
if err != nil { if err != nil {
return fmt.Errorf("failed to initialize database: %s", err) return err
} }
if key != "" {
log.Infof("update config with activity store key")
config.DataStoreEncryptionKey = key
err := updateMgmtConfig(mgmtConfig, config)
if err != nil {
return fmt.Errorf("failed to write out store encryption key: %s", err)
}
}
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain, accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
dnsDomain, eventStore, userDeleteFromIDPEnabled) dnsDomain, eventStore)
if err != nil { if err != nil {
return fmt.Errorf("failed to build default manager: %v", err) return fmt.Errorf("failed to build default manager: %v", err)
} }
@@ -209,11 +202,8 @@ var (
return fmt.Errorf("failed creating HTTP API handler: %v", err) return fmt.Errorf("failed creating HTTP API handler: %v", err)
} }
ephemeralManager := server.NewEphemeralManager(store, accountManager)
ephemeralManager.LoadInitialPeers()
gRPCAPIHandler := grpc.NewServer(gRPCOpts...) gRPCAPIHandler := grpc.NewServer(gRPCOpts...)
srv, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, appMetrics, ephemeralManager) srv, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, appMetrics)
if err != nil { if err != nil {
return fmt.Errorf("failed creating gRPC API handler: %v", err) return fmt.Errorf("failed creating gRPC API handler: %v", err)
} }
@@ -282,7 +272,6 @@ var (
SetupCloseHandler() SetupCloseHandler()
<-stopCh <-stopCh
ephemeralManager.Stop()
_ = appMetrics.Close() _ = appMetrics.Close()
_ = listener.Close() _ = listener.Close()
if certManager != nil { if certManager != nil {
@@ -298,20 +287,6 @@ var (
} }
) )
func initEventStore(dataDir string, key string) (activity.Store, string, error) {
var err error
if key == "" {
log.Debugf("generate new activity store encryption key")
key, err = sqlite.GenerateKey()
if err != nil {
return nil, "", err
}
}
store, err := sqlite.NewSQLiteStore(dataDir, key)
return store, key, err
}
func notifyStop(msg string) { func notifyStop(msg string) {
select { select {
case stopCh <- 1: case stopCh <- 1:
@@ -396,24 +371,24 @@ func handlerFunc(gRPCHandler *grpc.Server, httpHandler http.Handler) http.Handle
} }
func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) { func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
loadedConfig := &server.Config{} config := &server.Config{}
_, err := util.ReadJson(mgmtConfigPath, loadedConfig) _, err := util.ReadJson(mgmtConfigPath, config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if mgmtLetsencryptDomain != "" { if mgmtLetsencryptDomain != "" {
loadedConfig.HttpConfig.LetsEncryptDomain = mgmtLetsencryptDomain config.HttpConfig.LetsEncryptDomain = mgmtLetsencryptDomain
} }
if mgmtDataDir != "" { if mgmtDataDir != "" {
loadedConfig.Datadir = mgmtDataDir config.Datadir = mgmtDataDir
} }
if certKey != "" && certFile != "" { if certKey != "" && certFile != "" {
loadedConfig.HttpConfig.CertFile = certFile config.HttpConfig.CertFile = certFile
loadedConfig.HttpConfig.CertKey = certKey config.HttpConfig.CertKey = certKey
} }
oidcEndpoint := loadedConfig.HttpConfig.OIDCConfigEndpoint oidcEndpoint := config.HttpConfig.OIDCConfigEndpoint
if oidcEndpoint != "" { if oidcEndpoint != "" {
// if OIDCConfigEndpoint is specified, we can load DeviceAuthEndpoint and TokenEndpoint automatically // if OIDCConfigEndpoint is specified, we can load DeviceAuthEndpoint and TokenEndpoint automatically
log.Infof("loading OIDC configuration from the provided IDP configuration endpoint %s", oidcEndpoint) log.Infof("loading OIDC configuration from the provided IDP configuration endpoint %s", oidcEndpoint)
@@ -424,49 +399,45 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
log.Infof("loaded OIDC configuration from the provided IDP configuration endpoint: %s", oidcEndpoint) log.Infof("loaded OIDC configuration from the provided IDP configuration endpoint: %s", oidcEndpoint)
log.Infof("overriding HttpConfig.AuthIssuer with a new value %s, previously configured value: %s", log.Infof("overriding HttpConfig.AuthIssuer with a new value %s, previously configured value: %s",
oidcConfig.Issuer, loadedConfig.HttpConfig.AuthIssuer) oidcConfig.Issuer, config.HttpConfig.AuthIssuer)
loadedConfig.HttpConfig.AuthIssuer = oidcConfig.Issuer config.HttpConfig.AuthIssuer = oidcConfig.Issuer
log.Infof("overriding HttpConfig.AuthKeysLocation (JWT certs) with a new value %s, previously configured value: %s", log.Infof("overriding HttpConfig.AuthKeysLocation (JWT certs) with a new value %s, previously configured value: %s",
oidcConfig.JwksURI, loadedConfig.HttpConfig.AuthKeysLocation) oidcConfig.JwksURI, config.HttpConfig.AuthKeysLocation)
loadedConfig.HttpConfig.AuthKeysLocation = oidcConfig.JwksURI config.HttpConfig.AuthKeysLocation = oidcConfig.JwksURI
if !(loadedConfig.DeviceAuthorizationFlow == nil || strings.ToLower(loadedConfig.DeviceAuthorizationFlow.Provider) == string(server.NONE)) { if !(config.DeviceAuthorizationFlow == nil || strings.ToLower(config.DeviceAuthorizationFlow.Provider) == string(server.NONE)) {
log.Infof("overriding DeviceAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s", log.Infof("overriding DeviceAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s",
oidcConfig.TokenEndpoint, loadedConfig.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint) oidcConfig.TokenEndpoint, config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint)
loadedConfig.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint
log.Infof("overriding DeviceAuthorizationFlow.DeviceAuthEndpoint with a new value: %s, previously configured value: %s", log.Infof("overriding DeviceAuthorizationFlow.DeviceAuthEndpoint with a new value: %s, previously configured value: %s",
oidcConfig.DeviceAuthEndpoint, loadedConfig.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint) oidcConfig.DeviceAuthEndpoint, config.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint)
loadedConfig.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint = oidcConfig.DeviceAuthEndpoint config.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint = oidcConfig.DeviceAuthEndpoint
u, err := url.Parse(oidcEndpoint) u, err := url.Parse(oidcEndpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Infof("overriding DeviceAuthorizationFlow.ProviderConfig.Domain with a new value: %s, previously configured value: %s", log.Infof("overriding DeviceAuthorizationFlow.ProviderConfig.Domain with a new value: %s, previously configured value: %s",
u.Host, loadedConfig.DeviceAuthorizationFlow.ProviderConfig.Domain) u.Host, config.DeviceAuthorizationFlow.ProviderConfig.Domain)
loadedConfig.DeviceAuthorizationFlow.ProviderConfig.Domain = u.Host config.DeviceAuthorizationFlow.ProviderConfig.Domain = u.Host
if loadedConfig.DeviceAuthorizationFlow.ProviderConfig.Scope == "" { if config.DeviceAuthorizationFlow.ProviderConfig.Scope == "" {
loadedConfig.DeviceAuthorizationFlow.ProviderConfig.Scope = server.DefaultDeviceAuthFlowScope config.DeviceAuthorizationFlow.ProviderConfig.Scope = server.DefaultDeviceAuthFlowScope
} }
} }
if loadedConfig.PKCEAuthorizationFlow != nil { if config.PKCEAuthorizationFlow != nil {
log.Infof("overriding PKCEAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s", log.Infof("overriding PKCEAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s",
oidcConfig.TokenEndpoint, loadedConfig.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint) oidcConfig.TokenEndpoint, config.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint)
loadedConfig.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint
log.Infof("overriding PKCEAuthorizationFlow.AuthorizationEndpoint with a new value: %s, previously configured value: %s", log.Infof("overriding PKCEAuthorizationFlow.AuthorizationEndpoint with a new value: %s, previously configured value: %s",
oidcConfig.AuthorizationEndpoint, loadedConfig.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint) oidcConfig.AuthorizationEndpoint, config.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint)
loadedConfig.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint = oidcConfig.AuthorizationEndpoint config.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint = oidcConfig.AuthorizationEndpoint
} }
} }
return loadedConfig, err return config, err
}
func updateMgmtConfig(path string, config *server.Config) error {
return util.DirectWriteJson(path, config)
} }
// OIDCConfigResponse used for parsing OIDC config response // OIDCConfigResponse used for parsing OIDC config response

Some files were not shown because too many files have changed in this diff Show More