mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 08:16:39 +00:00
Compare commits
66 Commits
v0.8.1
...
feature/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45de3c1b06 | ||
|
|
04e4407ea7 | ||
|
|
06055af361 | ||
|
|
abd1230a69 | ||
|
|
f7de12daf8 | ||
|
|
c49fb0c40c | ||
|
|
6e9a162877 | ||
|
|
b4e03f4616 | ||
|
|
369a7ef345 | ||
|
|
c88e6a7342 | ||
|
|
2cd9b11e7d | ||
|
|
93d20e370b | ||
|
|
878ca6db22 | ||
|
|
2033650908 | ||
|
|
34c1c7d901 | ||
|
|
051fd3a4d7 | ||
|
|
af69a48745 | ||
|
|
68ff97ba84 | ||
|
|
c5705803a5 | ||
|
|
7e1ae448e0 | ||
|
|
518a2561a2 | ||
|
|
c75ffd0f4b | ||
|
|
e4ad6174ca | ||
|
|
6de313070a | ||
|
|
cd7d1a80c9 | ||
|
|
be7d829858 | ||
|
|
ed1872560f | ||
|
|
de898899a4 | ||
|
|
b63ec71aed | ||
|
|
1012172f04 | ||
|
|
788bb00ef1 | ||
|
|
4e5ee70b3d | ||
|
|
f1c00ae543 | ||
|
|
553a13588b | ||
|
|
586c0f5c3d | ||
|
|
c13f0b9f07 | ||
|
|
dd4ff61b51 | ||
|
|
e3657610bc | ||
|
|
e8733a37af | ||
|
|
3def84b111 | ||
|
|
47add9a9c3 | ||
|
|
09312b3e6d | ||
|
|
762a26dcea | ||
|
|
000ea72aec | ||
|
|
4b34a6d6df | ||
|
|
c39cd2f7b0 | ||
|
|
6dc3e8ca90 | ||
|
|
245863cd51 | ||
|
|
14e322d3f7 | ||
|
|
1be8c16e34 | ||
|
|
851de3fd4e | ||
|
|
c13288781f | ||
|
|
e34e0ccd12 | ||
|
|
95dc9cc16c | ||
|
|
d1c2b3d703 | ||
|
|
966661fe91 | ||
|
|
67ddaade58 | ||
|
|
138cf35e00 | ||
|
|
2555a6c3e8 | ||
|
|
86a66c6202 | ||
|
|
275d364df6 | ||
|
|
a3c5fa1307 | ||
|
|
75a69ca26b | ||
|
|
ae8e3ad6fe | ||
|
|
ff729f6755 | ||
|
|
7e1b20da5d |
7
.github/workflows/golang-test-darwin.yml
vendored
7
.github/workflows/golang-test-darwin.yml
vendored
@@ -1,5 +1,10 @@
|
|||||||
name: Test Code Darwin
|
name: Test Code Darwin
|
||||||
on: [push,pull_request]
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|||||||
59
.github/workflows/golang-test-linux.yml
vendored
59
.github/workflows/golang-test-linux.yml
vendored
@@ -1,5 +1,10 @@
|
|||||||
name: Test Code Linux
|
name: Test Code Linux
|
||||||
on: [push,pull_request]
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@@ -33,3 +38,55 @@ jobs:
|
|||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: GOARCH=${{ matrix.arch }} go test -exec 'sudo --preserve-env=CI' -timeout 5m -p 1 ./...
|
run: GOARCH=${{ matrix.arch }} go test -exec 'sudo --preserve-env=CI' -timeout 5m -p 1 ./...
|
||||||
|
|
||||||
|
test_client_on_docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.18.x
|
||||||
|
|
||||||
|
|
||||||
|
- name: Cache Go modules
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev
|
||||||
|
|
||||||
|
- name: Install modules
|
||||||
|
run: go mod tidy
|
||||||
|
|
||||||
|
- name: Generate Iface Test bin
|
||||||
|
run: go test -c -o iface-testing.bin ./iface/...
|
||||||
|
|
||||||
|
- name: Generate RouteManager Test bin
|
||||||
|
run: go test -c -o routemanager-testing.bin ./client/internal/routemanager/...
|
||||||
|
|
||||||
|
- name: Generate Engine Test bin
|
||||||
|
run: go test -c -o engine-testing.bin ./client/internal/*.go
|
||||||
|
|
||||||
|
- name: Generate Peer Test bin
|
||||||
|
run: go test -c -o peer-testing.bin ./client/internal/peer/...
|
||||||
|
|
||||||
|
- run: chmod +x *testing.bin
|
||||||
|
|
||||||
|
- name: Run Iface tests in docker
|
||||||
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/iface --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/iface-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
|
|
||||||
|
- name: Run RouteManager tests in docker
|
||||||
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal/routemanager --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/routemanager-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
|
|
||||||
|
- name: Run Engine tests in docker
|
||||||
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/engine-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
|
|
||||||
|
- name: Run Peer tests in docker
|
||||||
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal/peer --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/peer-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
7
.github/workflows/golang-test-windows.yml
vendored
7
.github/workflows/golang-test-windows.yml
vendored
@@ -1,5 +1,10 @@
|
|||||||
name: Test Code Windows
|
name: Test Code Windows
|
||||||
on: [push,pull_request]
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre:
|
pre:
|
||||||
|
|||||||
8
.github/workflows/golangci-lint.yml
vendored
8
.github/workflows/golangci-lint.yml
vendored
@@ -6,12 +6,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.18.x
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev
|
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
with:
|
with:
|
||||||
args: --timeout=6m
|
# SA1019: "io/ioutil" has been deprecated since Go 1.16
|
||||||
|
args: --timeout=6m -e SA1019
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
122
.github/workflows/release.yml
vendored
122
.github/workflows/release.yml
vendored
@@ -10,6 +10,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
SIGN_PIPE_VER: "v0.0.3"
|
SIGN_PIPE_VER: "v0.0.3"
|
||||||
|
GORELEASER_VER: "v1.6.3"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@@ -40,6 +41,9 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Install modules
|
name: Install modules
|
||||||
run: go mod tidy
|
run: go mod tidy
|
||||||
|
-
|
||||||
|
name: check git status
|
||||||
|
run: git --no-pager diff --exit-code
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
@@ -54,45 +58,75 @@ jobs:
|
|||||||
username: netbirdio
|
username: netbirdio
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-mingw-w64-x86-64
|
|
||||||
|
|
||||||
- name: Install rsrc
|
|
||||||
run: go install github.com/akavel/rsrc@v0.10.2
|
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
-
|
-
|
||||||
name: Run GoReleaser
|
name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v2
|
uses: goreleaser/goreleaser-action@v2
|
||||||
with:
|
with:
|
||||||
version: v1.6.3
|
version: ${{ env.GORELEASER_VER }}
|
||||||
args: release --rm-dist
|
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 }}
|
||||||
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: Trigger Windows binaries sign pipeline
|
|
||||||
uses: benc-uk/workflow-dispatch@v1
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
with:
|
|
||||||
workflow: Sign windows bin and installer
|
|
||||||
repo: netbirdio/sign-pipelines
|
|
||||||
ref: ${{ env.SIGN_PIPE_VER }}
|
|
||||||
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
|
||||||
inputs: '{ "tag": "${{ github.ref }}" }'
|
|
||||||
-
|
-
|
||||||
name: upload non tags for debug purposes
|
name: upload non tags for debug purposes
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: build
|
name: release
|
||||||
path: dist/
|
path: dist/
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
|
|
||||||
release_ui:
|
release_ui:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # It is required for GoReleaser to work properly
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.18
|
||||||
|
- name: Cache Go modules
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-ui-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-ui-go-
|
||||||
|
|
||||||
|
- name: Install modules
|
||||||
|
run: go mod tidy
|
||||||
|
|
||||||
|
- name: check git status
|
||||||
|
run: git --no-pager diff --exit-code
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-mingw-w64-x86-64
|
||||||
|
- name: Install rsrc
|
||||||
|
run: go install github.com/akavel/rsrc@v0.10.2
|
||||||
|
- 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
|
||||||
|
- name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v2
|
||||||
|
with:
|
||||||
|
version: ${{ env.GORELEASER_VER }}
|
||||||
|
args: release --config .goreleaser_ui.yaml --rm-dist
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
|
||||||
|
UPLOAD_DEBIAN_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
|
||||||
|
UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
|
||||||
|
- name: upload non tags for debug purposes
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: release-ui
|
||||||
|
path: dist/
|
||||||
|
retention-days: 3
|
||||||
|
|
||||||
|
release_ui_darwin:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
@@ -110,9 +144,9 @@ jobs:
|
|||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: ~/go/pkg/mod
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-ui-go-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go-
|
${{ runner.os }}-ui-go-
|
||||||
-
|
-
|
||||||
name: Install modules
|
name: Install modules
|
||||||
run: go mod tidy
|
run: go mod tidy
|
||||||
@@ -121,26 +155,42 @@ jobs:
|
|||||||
id: goreleaser
|
id: goreleaser
|
||||||
uses: goreleaser/goreleaser-action@v2
|
uses: goreleaser/goreleaser-action@v2
|
||||||
with:
|
with:
|
||||||
version: v1.6.3
|
version: ${{ env.GORELEASER_VER }}
|
||||||
args: release --config .goreleaser_ui_darwin.yaml --rm-dist
|
args: release --config .goreleaser_ui_darwin.yaml --rm-dist
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
-
|
-
|
||||||
name: Trigger Darwin App binaries sign pipeline
|
name: upload non tags for debug purposes
|
||||||
uses: benc-uk/workflow-dispatch@v1
|
uses: actions/upload-artifact@v2
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
with:
|
with:
|
||||||
workflow: Sign darwin ui app with dispatch
|
name: release-ui-darwin
|
||||||
|
path: dist/
|
||||||
|
retention-days: 3
|
||||||
|
|
||||||
|
trigger_windows_signer:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [release,release_ui]
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
steps:
|
||||||
|
- name: Trigger Windows binaries sign pipeline
|
||||||
|
uses: benc-uk/workflow-dispatch@v1
|
||||||
|
with:
|
||||||
|
workflow: Sign windows bin and installer
|
||||||
repo: netbirdio/sign-pipelines
|
repo: netbirdio/sign-pipelines
|
||||||
ref: ${{ env.SIGN_PIPE_VER }}
|
ref: ${{ env.SIGN_PIPE_VER }}
|
||||||
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
||||||
inputs: '{ "tag": "${{ github.ref }}" }'
|
inputs: '{ "tag": "${{ github.ref }}" }'
|
||||||
|
|
||||||
-
|
trigger_darwin_signer:
|
||||||
name: upload non tags for debug purposes
|
runs-on: ubuntu-latest
|
||||||
uses: actions/upload-artifact@v2
|
needs: release_ui_darwin
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
steps:
|
||||||
|
- name: Trigger Darwin App binaries sign pipeline
|
||||||
|
uses: benc-uk/workflow-dispatch@v1
|
||||||
with:
|
with:
|
||||||
name: build-ui-darwin
|
workflow: Sign darwin ui app with dispatch
|
||||||
path: dist/
|
repo: netbirdio/sign-pipelines
|
||||||
retention-days: 3
|
ref: ${{ env.SIGN_PIPE_VER }}
|
||||||
|
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
||||||
|
inputs: '{ "tag": "${{ github.ref }}" }'
|
||||||
46
.github/workflows/test-docker-compose-linux.yml
vendored
46
.github/workflows/test-docker-compose-linux.yml
vendored
@@ -1,10 +1,21 @@
|
|||||||
name: Test Docker Compose Linux
|
name: Test Docker Compose Linux
|
||||||
on: [push,pull_request]
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Install jq
|
||||||
|
run: sudo apt-get install -y jq
|
||||||
|
|
||||||
|
- name: Install curl
|
||||||
|
run: sudo apt-get install -y curl
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
@@ -28,22 +39,33 @@ jobs:
|
|||||||
working-directory: infrastructure_files
|
working-directory: infrastructure_files
|
||||||
run: bash -x configure.sh
|
run: bash -x configure.sh
|
||||||
env:
|
env:
|
||||||
CI_NETBIRD_AUTH0_DOMAIN: ${{ secrets.CI_NETBIRD_AUTH0_DOMAIN }}
|
CI_NETBIRD_AUTH_CLIENT_ID: ${{ secrets.CI_NETBIRD_AUTH_CLIENT_ID }}
|
||||||
CI_NETBIRD_AUTH0_CLIENT_ID: ${{ secrets.CI_NETBIRD_AUTH0_CLIENT_ID }}
|
CI_NETBIRD_AUTH_AUDIENCE: testing.ci
|
||||||
CI_NETBIRD_AUTH0_AUDIENCE: testing.ci
|
CI_NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT: https://example.eu.auth0.com/.well-known/openid-configuration
|
||||||
|
CI_NETBIRD_USE_AUTH0: true
|
||||||
|
|
||||||
- name: check values
|
- name: check values
|
||||||
working-directory: infrastructure_files
|
working-directory: infrastructure_files
|
||||||
env:
|
env:
|
||||||
CI_NETBIRD_AUTH0_DOMAIN: ${{ secrets.CI_NETBIRD_AUTH0_DOMAIN }}
|
CI_NETBIRD_AUTH_CLIENT_ID: ${{ secrets.CI_NETBIRD_AUTH_CLIENT_ID }}
|
||||||
CI_NETBIRD_AUTH0_CLIENT_ID: ${{ secrets.CI_NETBIRD_AUTH0_CLIENT_ID }}
|
CI_NETBIRD_AUTH_AUDIENCE: testing.ci
|
||||||
CI_NETBIRD_AUTH0_AUDIENCE: testing.ci
|
CI_NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT: https://example.eu.auth0.com/.well-known/openid-configuration
|
||||||
|
CI_NETBIRD_USE_AUTH0: true
|
||||||
|
CI_NETBIRD_AUTH_SUPPORTED_SCOPES: "openid profile email offline_access api email_verified"
|
||||||
|
CI_NETBIRD_AUTH_AUTHORITY: https://example.eu.auth0.com/
|
||||||
|
CI_NETBIRD_AUTH_JWT_CERTS: https://example.eu.auth0.com/.well-known/jwks.json
|
||||||
|
CI_NETBIRD_AUTH_TOKEN_ENDPOINT: https://example.eu.auth0.com/oauth/token
|
||||||
|
CI_NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT: https://example.eu.auth0.com/oauth/device/code
|
||||||
|
CI_NETBIRD_AUTH_REDIRECT_URI: "/peers"
|
||||||
run: |
|
run: |
|
||||||
grep AUTH0_DOMAIN docker-compose.yml | grep $CI_NETBIRD_AUTH0_DOMAIN
|
grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
||||||
grep AUTH0_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH0_CLIENT_ID
|
grep AUTH_AUTHORITY docker-compose.yml | grep $CI_NETBIRD_AUTH_AUTHORITY
|
||||||
grep AUTH0_AUDIENCE docker-compose.yml | grep $CI_NETBIRD_AUTH0_AUDIENCE
|
grep AUTH_AUDIENCE docker-compose.yml | grep $CI_NETBIRD_AUTH_AUDIENCE
|
||||||
grep NETBIRD_MGMT_API_ENDPOINT docker-compose.yml | grep "http://localhost:33071"
|
grep AUTH_SUPPORTED_SCOPES docker-compose.yml | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES"
|
||||||
grep NETBIRD_MGMT_GRPC_API_ENDPOINT docker-compose.yml | grep "http://localhost:33073"
|
grep USE_AUTH0 docker-compose.yml | grep $CI_NETBIRD_USE_AUTH0
|
||||||
|
grep NETBIRD_MGMT_API_ENDPOINT docker-compose.yml | grep "http://localhost:33073"
|
||||||
|
grep AUTH_REDIRECT_URI docker-compose.yml | grep $CI_NETBIRD_AUTH_REDIRECT_URI
|
||||||
|
grep AUTH_SILENT_REDIRECT_URI docker-compose.yml | egrep 'AUTH_SILENT_REDIRECT_URI=$'
|
||||||
|
|
||||||
- name: run docker compose up
|
- name: run docker compose up
|
||||||
working-directory: infrastructure_files
|
working-directory: infrastructure_files
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ builds:
|
|||||||
- arm64
|
- arm64
|
||||||
- arm
|
- arm
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
- -s -w -X github.com/netbirdio/netbird/client/system.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
|
|
||||||
- id: netbird-signal
|
- id: netbird-signal
|
||||||
@@ -58,88 +58,12 @@ builds:
|
|||||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
|
|
||||||
- id: netbird-ui
|
|
||||||
dir: client/ui
|
|
||||||
binary: netbird-ui
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=1
|
|
||||||
goos:
|
|
||||||
- linux
|
|
||||||
goarch:
|
|
||||||
- amd64
|
|
||||||
ldflags:
|
|
||||||
- -s -w -X github.com/netbirdio/netbird/client/system.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
|
||||||
|
|
||||||
- id: netbird-ui-windows
|
|
||||||
dir: client/ui
|
|
||||||
binary: netbird-ui
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=1
|
|
||||||
- CC=x86_64-w64-mingw32-gcc
|
|
||||||
goos:
|
|
||||||
- windows
|
|
||||||
goarch:
|
|
||||||
- amd64
|
|
||||||
ldflags:
|
|
||||||
- -s -w -X github.com/netbirdio/netbird/client/system.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
|
||||||
- -H windowsgui
|
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- builds:
|
- builds:
|
||||||
- netbird
|
- netbird
|
||||||
- id: linux-arch
|
|
||||||
name_template: "{{ .ProjectName }}-ui-linux_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
|
||||||
builds:
|
|
||||||
- netbird-ui
|
|
||||||
- id: windows-arch
|
|
||||||
name_template: "{{ .ProjectName }}-ui-windows_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
|
||||||
builds:
|
|
||||||
- netbird-ui-windows
|
|
||||||
|
|
||||||
nfpms:
|
nfpms:
|
||||||
|
|
||||||
- maintainer: Netbird <dev@netbird.io>
|
|
||||||
description: Netbird client UI.
|
|
||||||
homepage: https://netbird.io/
|
|
||||||
id: netbird-ui-deb
|
|
||||||
package_name: netbird-ui
|
|
||||||
builds:
|
|
||||||
- netbird-ui
|
|
||||||
formats:
|
|
||||||
- deb
|
|
||||||
contents:
|
|
||||||
- src: client/ui/netbird.desktop
|
|
||||||
dst: /usr/share/applications/netbird.desktop
|
|
||||||
- src: client/ui/disconnected.png
|
|
||||||
dst: /usr/share/pixmaps/netbird.png
|
|
||||||
dependencies:
|
|
||||||
- libayatana-appindicator3-1
|
|
||||||
- libgtk-3-dev
|
|
||||||
- libappindicator3-dev
|
|
||||||
- netbird
|
|
||||||
|
|
||||||
- maintainer: Netbird <dev@netbird.io>
|
|
||||||
description: Netbird client UI.
|
|
||||||
homepage: https://netbird.io/
|
|
||||||
id: netbird-ui-rpm
|
|
||||||
package_name: netbird-ui
|
|
||||||
builds:
|
|
||||||
- netbird-ui
|
|
||||||
formats:
|
|
||||||
- rpm
|
|
||||||
contents:
|
|
||||||
- src: client/ui/netbird.desktop
|
|
||||||
dst: /usr/share/applications/netbird.desktop
|
|
||||||
- src: client/ui/disconnected.png
|
|
||||||
dst: /usr/share/pixmaps/netbird.png
|
|
||||||
dependencies:
|
|
||||||
- libayatana-appindicator3-1
|
|
||||||
- libgtk-3-dev
|
|
||||||
- libappindicator3-dev
|
|
||||||
- netbird
|
|
||||||
|
|
||||||
- maintainer: Netbird <dev@netbird.io>
|
- maintainer: Netbird <dev@netbird.io>
|
||||||
description: Netbird client.
|
description: Netbird client.
|
||||||
homepage: https://netbird.io/
|
homepage: https://netbird.io/
|
||||||
@@ -431,7 +355,6 @@ uploads:
|
|||||||
- name: debian
|
- name: debian
|
||||||
ids:
|
ids:
|
||||||
- netbird-deb
|
- netbird-deb
|
||||||
- netbird-ui-deb
|
|
||||||
mode: archive
|
mode: archive
|
||||||
target: https://pkgs.wiretrustee.com/debian/pool/{{ .ArtifactName }};deb.distribution=stable;deb.component=main;deb.architecture={{ if .Arm }}armhf{{ else }}{{ .Arch }}{{ end }};deb.package=
|
target: https://pkgs.wiretrustee.com/debian/pool/{{ .ArtifactName }};deb.distribution=stable;deb.component=main;deb.architecture={{ if .Arm }}armhf{{ else }}{{ .Arch }}{{ end }};deb.package=
|
||||||
username: dev@wiretrustee.com
|
username: dev@wiretrustee.com
|
||||||
@@ -440,7 +363,6 @@ uploads:
|
|||||||
- name: yum
|
- name: yum
|
||||||
ids:
|
ids:
|
||||||
- netbird-rpm
|
- netbird-rpm
|
||||||
- netbird-ui-rpm
|
|
||||||
mode: archive
|
mode: archive
|
||||||
target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }}
|
target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }}
|
||||||
username: dev@wiretrustee.com
|
username: dev@wiretrustee.com
|
||||||
|
|||||||
98
.goreleaser_ui.yaml
Normal file
98
.goreleaser_ui.yaml
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
project_name: netbird-ui
|
||||||
|
builds:
|
||||||
|
- id: netbird-ui
|
||||||
|
dir: client/ui
|
||||||
|
binary: netbird-ui
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X github.com/netbirdio/netbird/client/system.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
|
|
||||||
|
- id: netbird-ui-windows
|
||||||
|
dir: client/ui
|
||||||
|
binary: netbird-ui
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=x86_64-w64-mingw32-gcc
|
||||||
|
goos:
|
||||||
|
- windows
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X github.com/netbirdio/netbird/client/system.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
|
- -H windowsgui
|
||||||
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- id: linux-arch
|
||||||
|
name_template: "{{ .ProjectName }}-linux_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
|
builds:
|
||||||
|
- netbird-ui
|
||||||
|
- id: windows-arch
|
||||||
|
name_template: "{{ .ProjectName }}-windows_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
|
builds:
|
||||||
|
- netbird-ui-windows
|
||||||
|
|
||||||
|
nfpms:
|
||||||
|
|
||||||
|
- maintainer: Netbird <dev@netbird.io>
|
||||||
|
description: Netbird client UI.
|
||||||
|
homepage: https://netbird.io/
|
||||||
|
id: netbird-ui-deb
|
||||||
|
package_name: netbird-ui
|
||||||
|
builds:
|
||||||
|
- netbird-ui
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
contents:
|
||||||
|
- src: client/ui/netbird.desktop
|
||||||
|
dst: /usr/share/applications/netbird.desktop
|
||||||
|
- src: client/ui/disconnected.png
|
||||||
|
dst: /usr/share/pixmaps/netbird.png
|
||||||
|
dependencies:
|
||||||
|
- libayatana-appindicator3-1
|
||||||
|
- libgtk-3-dev
|
||||||
|
- libappindicator3-dev
|
||||||
|
- netbird
|
||||||
|
|
||||||
|
- maintainer: Netbird <dev@netbird.io>
|
||||||
|
description: Netbird client UI.
|
||||||
|
homepage: https://netbird.io/
|
||||||
|
id: netbird-ui-rpm
|
||||||
|
package_name: netbird-ui
|
||||||
|
builds:
|
||||||
|
- netbird-ui
|
||||||
|
formats:
|
||||||
|
- rpm
|
||||||
|
contents:
|
||||||
|
- src: client/ui/netbird.desktop
|
||||||
|
dst: /usr/share/applications/netbird.desktop
|
||||||
|
- src: client/ui/disconnected.png
|
||||||
|
dst: /usr/share/pixmaps/netbird.png
|
||||||
|
dependencies:
|
||||||
|
- libayatana-appindicator3-1
|
||||||
|
- libgtk-3-dev
|
||||||
|
- libappindicator3-dev
|
||||||
|
- netbird
|
||||||
|
|
||||||
|
uploads:
|
||||||
|
- name: debian
|
||||||
|
ids:
|
||||||
|
- netbird-ui-deb
|
||||||
|
mode: archive
|
||||||
|
target: https://pkgs.wiretrustee.com/debian/pool/{{ .ArtifactName }};deb.distribution=stable;deb.component=main;deb.architecture={{ if .Arm }}armhf{{ else }}{{ .Arch }}{{ end }};deb.package=
|
||||||
|
username: dev@wiretrustee.com
|
||||||
|
method: PUT
|
||||||
|
|
||||||
|
- name: yum
|
||||||
|
ids:
|
||||||
|
- netbird-ui-rpm
|
||||||
|
mode: archive
|
||||||
|
target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }}
|
||||||
|
username: dev@wiretrustee.com
|
||||||
|
method: PUT
|
||||||
@@ -14,7 +14,7 @@ builds:
|
|||||||
- hardfloat
|
- hardfloat
|
||||||
- softfloat
|
- softfloat
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w -X github.com/netbirdio/netbird/client/ui/system.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
- -s -w -X github.com/netbirdio/netbird/client/system.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
tags:
|
tags:
|
||||||
- load_wgnt_from_rsrc
|
- load_wgnt_from_rsrc
|
||||||
@@ -23,5 +23,7 @@ archives:
|
|||||||
- builds:
|
- builds:
|
||||||
- netbird-ui-darwin
|
- netbird-ui-darwin
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
name_template: "{{ .ProjectName }}_darwin_checksums.txt"
|
||||||
changelog:
|
changelog:
|
||||||
skip: true
|
skip: true
|
||||||
31
README.md
31
README.md
@@ -16,7 +16,7 @@
|
|||||||
<a href="https://www.codacy.com/gh/netbirdio/netbird/dashboard?utm_source=github.com&utm_medium=referral&utm_content=netbirdio/netbird&utm_campaign=Badge_Grade"><img src="https://app.codacy.com/project/badge/Grade/e3013d046aec44cdb7462c8673b00976"/></a>
|
<a href="https://www.codacy.com/gh/netbirdio/netbird/dashboard?utm_source=github.com&utm_medium=referral&utm_content=netbirdio/netbird&utm_campaign=Badge_Grade"><img src="https://app.codacy.com/project/badge/Grade/e3013d046aec44cdb7462c8673b00976"/></a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A">
|
<a href="https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A">
|
||||||
<img src="https://img.shields.io/badge/slack-@wiretrustee-red.svg?logo=slack"/>
|
<img src="https://img.shields.io/badge/slack-@netbird-red.svg?logo=slack"/>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,28 +43,27 @@ It requires zero configuration effort leaving behind the hassle of opening ports
|
|||||||
NetBird creates an overlay peer-to-peer network connecting machines automatically regardless of their location (home, office, datacenter, container, cloud or edge environments) unifying virtual private network management experience.
|
NetBird creates an overlay peer-to-peer network connecting machines automatically regardless of their location (home, office, datacenter, container, cloud or edge environments) unifying virtual private network management experience.
|
||||||
|
|
||||||
**Key features:**
|
**Key features:**
|
||||||
- \[x] Automatic IP allocation and network management with a Web UI ([separate repo](https://github.com/netbirdio/dashboard))
|
- \[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] Automatic WireGuard peer (machine) discovery and configuration.
|
||||||
- \[x] Encrypted peer-to-peer connections without a central VPN gateway.
|
- \[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] Connection relay fallback in case a peer-to-peer connection is not possible.
|
||||||
- \[x] Desktop client applications for Linux, MacOS, and Windows (systray).
|
- \[x] Desktop client applications for Linux, MacOS, and Windows (systray).
|
||||||
- \[x] Multiuser support - sharing network between multiple users.
|
- \[x] Multiuser support - sharing network between multiple users.
|
||||||
- \[x] SSO and MFA support.
|
- \[x] SSO and MFA support.
|
||||||
- \[x] Multicloud and hybrid-cloud support.
|
- \[x] Multicloud and hybrid-cloud support.
|
||||||
- \[x] Kernel WireGuard usage when possible.
|
- \[x] Kernel WireGuard usage when possible.
|
||||||
- \[x] Access Controls - groups & rules.
|
- \[x] Access Controls - groups & rules.
|
||||||
- \[x] Remote SSH access without managing SSH keys.
|
- \[x] Remote SSH access without managing SSH keys.
|
||||||
|
- \[x] Network Routes.
|
||||||
|
|
||||||
**Coming soon:**
|
**Coming soon:**
|
||||||
- \[ ] Router nodes
|
|
||||||
- \[ ] Private DNS.
|
- \[ ] Private DNS.
|
||||||
- \[ ] Mobile clients.
|
- \[ ] Mobile clients.
|
||||||
- \[ ] Network Activity Monitoring.
|
- \[ ] Network Activity Monitoring.
|
||||||
|
|
||||||
### Secure peer-to-peer VPN with SSO and MFA in minutes
|
### Secure peer-to-peer VPN with SSO and MFA in minutes
|
||||||
<p float="left" align="middle">
|
<p float="left" align="middle">
|
||||||
<img src="docs/media/peerA.gif" width="400"/>
|
<img src="docs/media/netbird-sso-mfa-demo.gif" width="800"/>
|
||||||
<img src="docs/media/peerB.gif" width="400"/>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**Note**: The `main` branch may be in an *unstable or even broken state* during development.
|
**Note**: The `main` branch may be in an *unstable or even broken state* during development.
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
FROM gcr.io/distroless/base:debug
|
FROM gcr.io/distroless/base:debug
|
||||||
ENV WT_LOG_FILE=console
|
ENV WT_LOG_FILE=console
|
||||||
|
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
|
||||||
@@ -43,6 +43,8 @@ var loginCmd = &cobra.Command{
|
|||||||
return fmt.Errorf("get config file: %v", err)
|
return fmt.Errorf("get config file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config, _ = internal.UpdateOldManagementPort(ctx, config, configPath)
|
||||||
|
|
||||||
err = foregroundLogin(ctx, cmd, config, setupKey)
|
err = foregroundLogin(ctx, cmd, config, setupKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("foreground login failed: %v", err)
|
return fmt.Errorf("foreground login failed: %v", err)
|
||||||
@@ -167,7 +169,8 @@ func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *int
|
|||||||
hostedClient := internal.NewHostedDeviceFlow(
|
hostedClient := internal.NewHostedDeviceFlow(
|
||||||
providerConfig.ProviderConfig.Audience,
|
providerConfig.ProviderConfig.Audience,
|
||||||
providerConfig.ProviderConfig.ClientID,
|
providerConfig.ProviderConfig.ClientID,
|
||||||
providerConfig.ProviderConfig.Domain,
|
providerConfig.ProviderConfig.TokenEndpoint,
|
||||||
|
providerConfig.ProviderConfig.DeviceAuthEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
flowInfo, err := hostedClient.RequestDeviceCode(context.TODO())
|
flowInfo, err := hostedClient.RequestDeviceCode(context.TODO())
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
nbStatus "github.com/netbirdio/netbird/client/status"
|
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||||
|
"github.com/netbirdio/netbird/client/system"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -17,6 +19,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
detailFlag bool
|
detailFlag bool
|
||||||
|
ipv4Flag bool
|
||||||
ipsFilter []string
|
ipsFilter []string
|
||||||
statusFilter string
|
statusFilter string
|
||||||
ipsFilterMap map[string]struct{}
|
ipsFilterMap map[string]struct{}
|
||||||
@@ -72,7 +75,7 @@ var statusCmd = &cobra.Command{
|
|||||||
pbFullStatus := resp.GetFullStatus()
|
pbFullStatus := resp.GetFullStatus()
|
||||||
fullStatus := fromProtoFullStatus(pbFullStatus)
|
fullStatus := fromProtoFullStatus(pbFullStatus)
|
||||||
|
|
||||||
cmd.Print(parseFullStatus(fullStatus, detailFlag, daemonStatus))
|
cmd.Print(parseFullStatus(fullStatus, detailFlag, daemonStatus, resp.GetDaemonVersion(), ipv4Flag))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@@ -81,8 +84,9 @@ var statusCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
ipsFilterMap = make(map[string]struct{})
|
ipsFilterMap = make(map[string]struct{})
|
||||||
statusCmd.PersistentFlags().BoolVarP(&detailFlag, "detail", "d", false, "display detailed status information")
|
statusCmd.PersistentFlags().BoolVarP(&detailFlag, "detail", "d", false, "display detailed status information")
|
||||||
statusCmd.PersistentFlags().StringSliceVar(&ipsFilter, "filter-by-ips", []string{}, "filters the detailed output by a list of one or more IPs, e.g. --filter-by-ips 100.64.0.100,100.64.0.200")
|
statusCmd.PersistentFlags().BoolVar(&ipv4Flag, "ipv4", false, "display only NetBird IPv4 of this peer, e.g., --ipv4 will output 100.64.0.33")
|
||||||
statusCmd.PersistentFlags().StringVar(&statusFilter, "filter-by-status", "", "filters the detailed output by connection status(connected|disconnected), e.g. --filter-by-status connected")
|
statusCmd.PersistentFlags().StringSliceVar(&ipsFilter, "filter-by-ips", []string{}, "filters the detailed output by a list of one or more IPs, e.g., --filter-by-ips 100.64.0.100,100.64.0.200")
|
||||||
|
statusCmd.PersistentFlags().StringVar(&statusFilter, "filter-by-status", "", "filters the detailed output by connection status(connected|disconnected), e.g., --filter-by-status connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFilters() error {
|
func parseFilters() error {
|
||||||
@@ -106,29 +110,32 @@ func parseFilters() error {
|
|||||||
|
|
||||||
func fromProtoFullStatus(pbFullStatus *proto.FullStatus) nbStatus.FullStatus {
|
func fromProtoFullStatus(pbFullStatus *proto.FullStatus) nbStatus.FullStatus {
|
||||||
var fullStatus nbStatus.FullStatus
|
var fullStatus nbStatus.FullStatus
|
||||||
fullStatus.ManagementState.URL = pbFullStatus.ManagementState.URL
|
managementState := pbFullStatus.GetManagementState()
|
||||||
fullStatus.ManagementState.Connected = pbFullStatus.ManagementState.Connected
|
fullStatus.ManagementState.URL = managementState.GetURL()
|
||||||
|
fullStatus.ManagementState.Connected = managementState.GetConnected()
|
||||||
|
|
||||||
fullStatus.SignalState.URL = pbFullStatus.SignalState.URL
|
signalState := pbFullStatus.GetSignalState()
|
||||||
fullStatus.SignalState.Connected = pbFullStatus.SignalState.Connected
|
fullStatus.SignalState.URL = signalState.GetURL()
|
||||||
|
fullStatus.SignalState.Connected = signalState.GetConnected()
|
||||||
|
|
||||||
fullStatus.LocalPeerState.IP = pbFullStatus.LocalPeerState.IP
|
localPeerState := pbFullStatus.GetLocalPeerState()
|
||||||
fullStatus.LocalPeerState.PubKey = pbFullStatus.LocalPeerState.PubKey
|
fullStatus.LocalPeerState.IP = localPeerState.GetIP()
|
||||||
fullStatus.LocalPeerState.KernelInterface = pbFullStatus.LocalPeerState.KernelInterface
|
fullStatus.LocalPeerState.PubKey = localPeerState.GetPubKey()
|
||||||
|
fullStatus.LocalPeerState.KernelInterface = localPeerState.GetKernelInterface()
|
||||||
|
|
||||||
var peersState []nbStatus.PeerState
|
var peersState []nbStatus.PeerState
|
||||||
|
|
||||||
for _, pbPeerState := range pbFullStatus.Peers {
|
for _, pbPeerState := range pbFullStatus.GetPeers() {
|
||||||
timeLocal := pbPeerState.ConnStatusUpdate.AsTime().Local()
|
timeLocal := pbPeerState.GetConnStatusUpdate().AsTime().Local()
|
||||||
peerState := nbStatus.PeerState{
|
peerState := nbStatus.PeerState{
|
||||||
IP: pbPeerState.IP,
|
IP: pbPeerState.GetIP(),
|
||||||
PubKey: pbPeerState.PubKey,
|
PubKey: pbPeerState.GetPubKey(),
|
||||||
ConnStatus: pbPeerState.ConnStatus,
|
ConnStatus: pbPeerState.GetConnStatus(),
|
||||||
ConnStatusUpdate: timeLocal,
|
ConnStatusUpdate: timeLocal,
|
||||||
Relayed: pbPeerState.Relayed,
|
Relayed: pbPeerState.GetRelayed(),
|
||||||
Direct: pbPeerState.Direct,
|
Direct: pbPeerState.GetDirect(),
|
||||||
LocalIceCandidateType: pbPeerState.LocalIceCandidateType,
|
LocalIceCandidateType: pbPeerState.GetLocalIceCandidateType(),
|
||||||
RemoteIceCandidateType: pbPeerState.RemoteIceCandidateType,
|
RemoteIceCandidateType: pbPeerState.GetRemoteIceCandidateType(),
|
||||||
}
|
}
|
||||||
peersState = append(peersState, peerState)
|
peersState = append(peersState, peerState)
|
||||||
}
|
}
|
||||||
@@ -138,7 +145,19 @@ func fromProtoFullStatus(pbFullStatus *proto.FullStatus) nbStatus.FullStatus {
|
|||||||
return fullStatus
|
return fullStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonStatus string) string {
|
func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonStatus string, daemonVersion string, flag bool) string {
|
||||||
|
|
||||||
|
interfaceIP := fullStatus.LocalPeerState.IP
|
||||||
|
|
||||||
|
ip, _, err := net.ParseCIDR(interfaceIP)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv4Flag {
|
||||||
|
return fmt.Sprintf("%s\n", ip)
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
managementStatusURL = ""
|
managementStatusURL = ""
|
||||||
signalStatusURL = ""
|
signalStatusURL = ""
|
||||||
@@ -160,8 +179,6 @@ func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonSta
|
|||||||
signalConnString = "Connected"
|
signalConnString = "Connected"
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaceIP := fullStatus.LocalPeerState.IP
|
|
||||||
|
|
||||||
if fullStatus.LocalPeerState.KernelInterface {
|
if fullStatus.LocalPeerState.KernelInterface {
|
||||||
interfaceTypeString = "Kernel"
|
interfaceTypeString = "Kernel"
|
||||||
} else if fullStatus.LocalPeerState.IP == "" {
|
} else if fullStatus.LocalPeerState.IP == "" {
|
||||||
@@ -174,12 +191,16 @@ func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonSta
|
|||||||
peersCountString := fmt.Sprintf("%d/%d Connected", peersConnected, len(fullStatus.Peers))
|
peersCountString := fmt.Sprintf("%d/%d Connected", peersConnected, len(fullStatus.Peers))
|
||||||
|
|
||||||
summary := fmt.Sprintf(
|
summary := fmt.Sprintf(
|
||||||
"%s"+ // daemon status
|
"Daemon version: %s\n"+
|
||||||
|
"CLI version: %s\n"+
|
||||||
|
"%s"+ // daemon status
|
||||||
"Management: %s%s\n"+
|
"Management: %s%s\n"+
|
||||||
"Signal: %s%s\n"+
|
"Signal: %s%s\n"+
|
||||||
"NetBird IP: %s\n"+
|
"NetBird IP: %s\n"+
|
||||||
"Interface type: %s\n"+
|
"Interface type: %s\n"+
|
||||||
"Peers count: %s\n",
|
"Peers count: %s\n",
|
||||||
|
daemonVersion,
|
||||||
|
system.NetbirdVersion(),
|
||||||
daemonStatus,
|
daemonStatus,
|
||||||
managementConnString,
|
managementConnString,
|
||||||
managementStatusURL,
|
managementStatusURL,
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ var upCmd = &cobra.Command{
|
|||||||
return fmt.Errorf("get config file: %v", err)
|
return fmt.Errorf("get config file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config, _ = internal.UpdateOldManagementPort(ctx, config, configPath)
|
||||||
|
|
||||||
err = foregroundLogin(ctx, cmd, config, setupKey)
|
err = foregroundLogin(ctx, cmd, config, setupKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("foreground login failed: %v", err)
|
return fmt.Errorf("foreground login failed: %v", err)
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ done:
|
|||||||
Pop $2
|
Pop $2
|
||||||
Exch $1
|
Exch $1
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
|
||||||
!macro GetAppFromCommand in out
|
!macro GetAppFromCommand in out
|
||||||
Push "${in}"
|
Push "${in}"
|
||||||
Call GetAppFromCommand
|
Call GetAppFromCommand
|
||||||
@@ -117,7 +118,7 @@ Call GetAppFromCommand ; Remove quotes and parameters from UninstCommand
|
|||||||
Pop $0
|
Pop $0
|
||||||
Pop $1
|
Pop $1
|
||||||
GetFullPathName $2 "$0\.."
|
GetFullPathName $2 "$0\.."
|
||||||
ExecWait '"$0" $1 _?=$2'
|
ExecWait '"$0" /S $1 _?=$2'
|
||||||
Delete "$0" ; Extra cleanup because we used _?=
|
Delete "$0" ; Extra cleanup because we used _?=
|
||||||
RMDir "$2"
|
RMDir "$2"
|
||||||
Pop $2
|
Pop $2
|
||||||
@@ -126,30 +127,27 @@ Pop $0
|
|||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
Function .onInit
|
Function .onInit
|
||||||
|
StrCpy $INSTDIR "${INSTALL_DIR}"
|
||||||
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Wiretrustee" "UninstallString"
|
|
||||||
${If} $R0 != ""
|
|
||||||
MessageBox MB_YESNO|MB_ICONQUESTION "Wiretrustee is installed. We must remove it before installing Netbird. Procced?" IDNO noWTUninstOld
|
|
||||||
!insertmacro UninstallPreviousNSIS $R0 "/NoMsgBox"
|
|
||||||
noWTUninstOld:
|
|
||||||
${EndIf}
|
|
||||||
|
|
||||||
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\$(^NAME)" "UninstallString"
|
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\$(^NAME)" "UninstallString"
|
||||||
${If} $R0 != ""
|
${If} $R0 != ""
|
||||||
MessageBox MB_YESNO|MB_ICONQUESTION "$(^NAME) is already installed. Do you want to remove the previous version?" IDNO noUninstOld
|
# if silent install jump to uninstall step
|
||||||
!insertmacro UninstallPreviousNSIS $R0 "/NoMsgBox"
|
IfSilent uninstall
|
||||||
noUninstOld:
|
|
||||||
|
MessageBox MB_YESNO|MB_ICONQUESTION "NetBird is already installed. We must remove it before installing upgrading NetBird. Proceed?" IDNO done IDYES uninstall
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
!insertmacro UninstallPreviousNSIS $R0 "/NoMsgBox"
|
||||||
|
done:
|
||||||
|
|
||||||
${EndIf}
|
${EndIf}
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
######################################################################
|
######################################################################
|
||||||
Section -MainProgram
|
Section -MainProgram
|
||||||
${INSTALL_TYPE}
|
${INSTALL_TYPE}
|
||||||
SetOverwrite ifnewer
|
# SetOverwrite ifnewer
|
||||||
SetOutPath "$INSTDIR"
|
SetOutPath "$INSTDIR"
|
||||||
File /r "..\\dist\\netbird_windows_amd64\\"
|
File /r "..\\dist\\netbird_windows_amd64\\"
|
||||||
|
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
Section -Icons_Reg
|
Section -Icons_Reg
|
||||||
@@ -172,24 +170,29 @@ SetShellVarContext current
|
|||||||
CreateShortCut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
|
CreateShortCut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
|
||||||
CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
|
CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
|
||||||
SetShellVarContext all
|
SetShellVarContext all
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section -Post
|
||||||
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service install'
|
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service install'
|
||||||
Exec '"$INSTDIR\${MAIN_APP_EXE}" service start'
|
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service start'
|
||||||
# sleep a bit for visibility
|
# sleep a bit for visibility
|
||||||
Sleep 1000
|
Sleep 1000
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
Section Uninstall
|
Section Uninstall
|
||||||
${INSTALL_TYPE}
|
${INSTALL_TYPE}
|
||||||
|
|
||||||
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service stop'
|
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service stop'
|
||||||
Exec '"$INSTDIR\${MAIN_APP_EXE}" service uninstall'
|
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service uninstall'
|
||||||
|
|
||||||
# kill ui client
|
# kill ui client
|
||||||
ExecWait `taskkill /im ${UI_APP_EXE}.exe`
|
ExecWait `taskkill /im ${UI_APP_EXE}.exe`
|
||||||
|
|
||||||
# wait the service uninstall take unblock the executable
|
# wait the service uninstall take unblock the executable
|
||||||
Sleep 3000
|
Sleep 3000
|
||||||
|
Delete "$INSTDIR\${UI_APP_EXE}"
|
||||||
|
Delete "$INSTDIR\${MAIN_APP_EXE}"
|
||||||
RmDir /r "$INSTDIR"
|
RmDir /r "$INSTDIR"
|
||||||
|
|
||||||
SetShellVarContext current
|
SetShellVarContext current
|
||||||
@@ -209,4 +212,4 @@ SetShellVarContext current
|
|||||||
SetOutPath $INSTDIR
|
SetOutPath $INSTDIR
|
||||||
ShellExecAsUser::ShellExecAsUser "" "$DESKTOP\${APP_NAME}.lnk"
|
ShellExecAsUser::ShellExecAsUser "" "$DESKTOP\${APP_NAME}.lnk"
|
||||||
SetShellVarContext all
|
SetShellVarContext all
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func ManagementURLDefault() *url.URL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
managementURL, err := parseURL("Management URL", "https://api.wiretrustee.com:33073")
|
managementURL, err := ParseURL("Management URL", "https://api.wiretrustee.com:443")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -37,6 +37,7 @@ type Config struct {
|
|||||||
ManagementURL *url.URL
|
ManagementURL *url.URL
|
||||||
AdminURL *url.URL
|
AdminURL *url.URL
|
||||||
WgIface string
|
WgIface string
|
||||||
|
WgPort int
|
||||||
IFaceBlackList []string
|
IFaceBlackList []string
|
||||||
// SSHKey is a private SSH key in a PEM format
|
// SSHKey is a private SSH key in a PEM format
|
||||||
SSHKey string
|
SSHKey string
|
||||||
@@ -49,9 +50,15 @@ func createNewConfig(managementURL, adminURL, configPath, preSharedKey string) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config := &Config{SSHKey: string(pem), PrivateKey: wgKey, WgIface: iface.WgInterfaceDefault, IFaceBlackList: []string{}}
|
config := &Config{
|
||||||
|
SSHKey: string(pem),
|
||||||
|
PrivateKey: wgKey,
|
||||||
|
WgIface: iface.WgInterfaceDefault,
|
||||||
|
WgPort: iface.DefaultWgPort,
|
||||||
|
IFaceBlackList: []string{},
|
||||||
|
}
|
||||||
if managementURL != "" {
|
if managementURL != "" {
|
||||||
URL, err := parseURL("Management URL", managementURL)
|
URL, err := ParseURL("Management URL", managementURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -64,8 +71,16 @@ func createNewConfig(managementURL, adminURL, configPath, preSharedKey string) (
|
|||||||
config.PreSharedKey = preSharedKey
|
config.PreSharedKey = preSharedKey
|
||||||
}
|
}
|
||||||
|
|
||||||
config.IFaceBlackList = []string{iface.WgInterfaceDefault, "tun0", "zt", "ZeroTier", "utun", "wg", "ts",
|
if adminURL != "" {
|
||||||
"Tailscale", "tailscale"}
|
newURL, err := ParseURL("Admin Panel URL", adminURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.AdminURL = newURL
|
||||||
|
}
|
||||||
|
|
||||||
|
config.IFaceBlackList = []string{iface.WgInterfaceDefault, "wt", "utun", "tun0", "zt", "ZeroTier", "utun", "wg", "ts",
|
||||||
|
"Tailscale", "tailscale", "docker", "vet"}
|
||||||
|
|
||||||
err = util.WriteJson(configPath, config)
|
err = util.WriteJson(configPath, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -75,7 +90,8 @@ func createNewConfig(managementURL, adminURL, configPath, preSharedKey string) (
|
|||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseURL(serviceName, managementURL string) (*url.URL, error) {
|
// ParseURL parses and validates management URL
|
||||||
|
func ParseURL(serviceName, managementURL string) (*url.URL, error) {
|
||||||
parsedMgmtURL, err := url.ParseRequestURI(managementURL)
|
parsedMgmtURL, err := url.ParseRequestURI(managementURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed parsing management URL %s: [%s]", managementURL, err.Error())
|
log.Errorf("failed parsing management URL %s: [%s]", managementURL, err.Error())
|
||||||
@@ -107,7 +123,7 @@ func ReadConfig(managementURL, adminURL, configPath string, preSharedKey *string
|
|||||||
if managementURL != "" && config.ManagementURL.String() != managementURL {
|
if managementURL != "" && config.ManagementURL.String() != managementURL {
|
||||||
log.Infof("new Management URL provided, updated to %s (old value %s)",
|
log.Infof("new Management URL provided, updated to %s (old value %s)",
|
||||||
managementURL, config.ManagementURL)
|
managementURL, config.ManagementURL)
|
||||||
newURL, err := parseURL("Management URL", managementURL)
|
newURL, err := ParseURL("Management URL", managementURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -118,7 +134,7 @@ func ReadConfig(managementURL, adminURL, configPath string, preSharedKey *string
|
|||||||
if adminURL != "" && (config.AdminURL == nil || config.AdminURL.String() != adminURL) {
|
if adminURL != "" && (config.AdminURL == nil || config.AdminURL.String() != adminURL) {
|
||||||
log.Infof("new Admin Panel URL provided, updated to %s (old value %s)",
|
log.Infof("new Admin Panel URL provided, updated to %s (old value %s)",
|
||||||
adminURL, config.AdminURL)
|
adminURL, config.AdminURL)
|
||||||
newURL, err := parseURL("Admin Panel URL", adminURL)
|
newURL, err := ParseURL("Admin Panel URL", adminURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -141,6 +157,11 @@ func ReadConfig(managementURL, adminURL, configPath string, preSharedKey *string
|
|||||||
refresh = true
|
refresh = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.WgPort == 0 {
|
||||||
|
config.WgPort = iface.DefaultWgPort
|
||||||
|
refresh = true
|
||||||
|
}
|
||||||
|
|
||||||
if refresh {
|
if refresh {
|
||||||
// since we have new management URL, we need to update config file
|
// since we have new management URL, we need to update config file
|
||||||
if err := util.WriteJson(configPath, config); err != nil {
|
if err := util.WriteJson(configPath, config); err != nil {
|
||||||
@@ -188,9 +209,14 @@ type ProviderConfig struct {
|
|||||||
// ClientSecret An IDP application client secret
|
// ClientSecret An IDP application client secret
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
// Domain An IDP API domain
|
// Domain An IDP API domain
|
||||||
|
// Deprecated. Use OIDCConfigEndpoint instead
|
||||||
Domain string
|
Domain string
|
||||||
// Audience An Audience for to authorization validation
|
// Audience An Audience for to authorization validation
|
||||||
Audience string
|
Audience string
|
||||||
|
// TokenEndpoint is the endpoint of an IDP manager where clients can obtain access token
|
||||||
|
TokenEndpoint string
|
||||||
|
// DeviceAuthEndpoint is the endpoint of an IDP manager where clients can obtain device authorization code
|
||||||
|
DeviceAuthEndpoint string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDeviceAuthorizationFlowInfo(ctx context.Context, config *Config) (DeviceAuthorizationFlow, error) {
|
func GetDeviceAuthorizationFlowInfo(ctx context.Context, config *Config) (DeviceAuthorizationFlow, error) {
|
||||||
@@ -212,7 +238,13 @@ func GetDeviceAuthorizationFlowInfo(ctx context.Context, config *Config) (Device
|
|||||||
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
|
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
|
||||||
return DeviceAuthorizationFlow{}, err
|
return DeviceAuthorizationFlow{}, err
|
||||||
}
|
}
|
||||||
log.Debugf("connected to management Service %s", config.ManagementURL.String())
|
log.Debugf("connected to the Management service %s", config.ManagementURL.String())
|
||||||
|
defer func() {
|
||||||
|
err = mgmClient.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to close the Management service client %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
serverKey, err := mgmClient.GetServerPublicKey()
|
serverKey, err := mgmClient.GetServerPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -231,20 +263,40 @@ func GetDeviceAuthorizationFlowInfo(ctx context.Context, config *Config) (Device
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mgmClient.Close()
|
deviceAuthorizationFlow := DeviceAuthorizationFlow{
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed closing Management Service client: %v", err)
|
|
||||||
return DeviceAuthorizationFlow{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return DeviceAuthorizationFlow{
|
|
||||||
Provider: protoDeviceAuthorizationFlow.Provider.String(),
|
Provider: protoDeviceAuthorizationFlow.Provider.String(),
|
||||||
|
|
||||||
ProviderConfig: ProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
Audience: protoDeviceAuthorizationFlow.ProviderConfig.Audience,
|
Audience: protoDeviceAuthorizationFlow.GetProviderConfig().GetAudience(),
|
||||||
ClientID: protoDeviceAuthorizationFlow.ProviderConfig.ClientID,
|
ClientID: protoDeviceAuthorizationFlow.GetProviderConfig().GetClientID(),
|
||||||
ClientSecret: protoDeviceAuthorizationFlow.ProviderConfig.ClientSecret,
|
ClientSecret: protoDeviceAuthorizationFlow.GetProviderConfig().GetClientSecret(),
|
||||||
Domain: protoDeviceAuthorizationFlow.ProviderConfig.Domain,
|
Domain: protoDeviceAuthorizationFlow.GetProviderConfig().Domain,
|
||||||
|
TokenEndpoint: protoDeviceAuthorizationFlow.GetProviderConfig().GetTokenEndpoint(),
|
||||||
|
DeviceAuthEndpoint: protoDeviceAuthorizationFlow.GetProviderConfig().GetDeviceAuthEndpoint(),
|
||||||
},
|
},
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
err = isProviderConfigValid(deviceAuthorizationFlow.ProviderConfig)
|
||||||
|
if err != nil {
|
||||||
|
return DeviceAuthorizationFlow{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceAuthorizationFlow, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProviderConfigValid(config ProviderConfig) error {
|
||||||
|
errorMSGFormat := "invalid provider configuration received from management: %s value is empty. Contact your NetBird administrator"
|
||||||
|
if config.Audience == "" {
|
||||||
|
return fmt.Errorf(errorMSGFormat, "Audience")
|
||||||
|
}
|
||||||
|
if config.ClientID == "" {
|
||||||
|
return fmt.Errorf(errorMSGFormat, "Client ID")
|
||||||
|
}
|
||||||
|
if config.TokenEndpoint == "" {
|
||||||
|
return fmt.Errorf(errorMSGFormat, "Token Endpoint")
|
||||||
|
}
|
||||||
|
if config.DeviceAuthEndpoint == "" {
|
||||||
|
return fmt.Errorf(errorMSGFormat, "Device Auth Endpoint")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,9 +79,21 @@ func RunClient(ctx context.Context, config *Config, statusRecorder *nbStatus.Sta
|
|||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
log.Debugf("conecting to the Management service %s", config.ManagementURL.Host)
|
||||||
|
mgmClient, err := mgm.NewClient(engineCtx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||||
|
if err != nil {
|
||||||
|
return wrapErr(gstatus.Errorf(codes.FailedPrecondition, "failed connecting to Management Service : %s", err))
|
||||||
|
}
|
||||||
|
log.Debugf("connected to the Management service %s", config.ManagementURL.Host)
|
||||||
|
defer func() {
|
||||||
|
err = mgmClient.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to close the Management service client %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config
|
// connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config
|
||||||
mgmClient, loginResp, err := connectToManagement(engineCtx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled,
|
loginResp, err := loginToManagement(engineCtx, mgmClient, publicSSHKey)
|
||||||
publicSSHKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug(err)
|
log.Debug(err)
|
||||||
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.PermissionDenied) {
|
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.PermissionDenied) {
|
||||||
@@ -95,7 +107,7 @@ func RunClient(ctx context.Context, config *Config, statusRecorder *nbStatus.Sta
|
|||||||
localPeerState := nbStatus.LocalPeerState{
|
localPeerState := nbStatus.LocalPeerState{
|
||||||
IP: loginResp.GetPeerConfig().GetAddress(),
|
IP: loginResp.GetPeerConfig().GetAddress(),
|
||||||
PubKey: myPrivateKey.PublicKey().String(),
|
PubKey: myPrivateKey.PublicKey().String(),
|
||||||
KernelInterface: iface.WireguardModExists(),
|
KernelInterface: iface.WireguardModuleIsLoaded(),
|
||||||
}
|
}
|
||||||
|
|
||||||
statusRecorder.UpdateLocalPeerState(localPeerState)
|
statusRecorder.UpdateLocalPeerState(localPeerState)
|
||||||
@@ -114,6 +126,12 @@ func RunClient(ctx context.Context, config *Config, statusRecorder *nbStatus.Sta
|
|||||||
log.Error(err)
|
log.Error(err)
|
||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
err = signalClient.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed closing Signal service client %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
statusRecorder.MarkSignalConnected(signalURL)
|
statusRecorder.MarkSignalConnected(signalURL)
|
||||||
|
|
||||||
@@ -139,18 +157,6 @@ func RunClient(ctx context.Context, config *Config, statusRecorder *nbStatus.Sta
|
|||||||
|
|
||||||
backOff.Reset()
|
backOff.Reset()
|
||||||
|
|
||||||
err = mgmClient.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed closing Management Service client %v", err)
|
|
||||||
return wrapErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = signalClient.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed closing Signal Service client %v", err)
|
|
||||||
return wrapErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = engine.Stop()
|
err = engine.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed stopping engine %v", err)
|
log.Errorf("failed stopping engine %v", err)
|
||||||
@@ -182,7 +188,7 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe
|
|||||||
WgAddr: peerConfig.Address,
|
WgAddr: peerConfig.Address,
|
||||||
IFaceBlackList: config.IFaceBlackList,
|
IFaceBlackList: config.IFaceBlackList,
|
||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: iface.DefaultWgPort,
|
WgPort: config.WgPort,
|
||||||
SSHKey: []byte(config.SSHKey),
|
SSHKey: []byte(config.SSHKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,27 +221,93 @@ func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig,
|
|||||||
return signalClient, nil
|
return signalClient, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// connectToManagement creates Management Services client, establishes a connection, logs-in and gets a global Wiretrustee config (signal, turn, stun hosts, etc)
|
// loginToManagement creates Management Services client, establishes a connection, logs-in and gets a global Wiretrustee config (signal, turn, stun hosts, etc)
|
||||||
func connectToManagement(ctx context.Context, managementAddr string, ourPrivateKey wgtypes.Key, tlsEnabled bool, pubSSHKey []byte) (*mgm.GrpcClient, *mgmProto.LoginResponse, error) {
|
func loginToManagement(ctx context.Context, client mgm.Client, pubSSHKey []byte) (*mgmProto.LoginResponse, error) {
|
||||||
log.Debugf("connecting to Management Service %s", managementAddr)
|
|
||||||
client, err := mgm.NewClient(ctx, managementAddr, ourPrivateKey, tlsEnabled)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, gstatus.Errorf(codes.FailedPrecondition, "failed connecting to Management Service : %s", err)
|
|
||||||
}
|
|
||||||
log.Debugf("connected to management server %s", managementAddr)
|
|
||||||
|
|
||||||
serverPublicKey, err := client.GetServerPublicKey()
|
serverPublicKey, err := client.GetServerPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, gstatus.Errorf(codes.FailedPrecondition, "failed while getting Management Service public key: %s", err)
|
return nil, gstatus.Errorf(codes.FailedPrecondition, "failed while getting Management Service public key: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sysInfo := system.GetInfo(ctx)
|
sysInfo := system.GetInfo(ctx)
|
||||||
loginResp, err := client.Login(*serverPublicKey, sysInfo, pubSSHKey)
|
loginResp, err := client.Login(*serverPublicKey, sysInfo, pubSSHKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("peer logged in to Management Service %s", managementAddr)
|
return loginResp, nil
|
||||||
|
}
|
||||||
return client, loginResp, nil
|
|
||||||
|
// ManagementLegacyPort is the port that was used before by the Management gRPC server.
|
||||||
|
// It is used for backward compatibility now.
|
||||||
|
// NB: hardcoded from github.com/netbirdio/netbird/management/cmd to avoid import
|
||||||
|
const ManagementLegacyPort = 33073
|
||||||
|
|
||||||
|
// UpdateOldManagementPort checks whether client can switch to the new Management port 443.
|
||||||
|
// If it can switch, then it updates the config and returns a new one. Otherwise, it returns the provided config.
|
||||||
|
// The check is performed only for the NetBird's managed version.
|
||||||
|
func UpdateOldManagementPort(ctx context.Context, config *Config, configPath string) (*Config, error) {
|
||||||
|
|
||||||
|
if config.ManagementURL.Hostname() != ManagementURLDefault().Hostname() {
|
||||||
|
// only do the check for the NetBird's managed version
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var mgmTlsEnabled bool
|
||||||
|
if config.ManagementURL.Scheme == "https" {
|
||||||
|
mgmTlsEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mgmTlsEnabled {
|
||||||
|
// only do the check for HTTPs scheme (the hosted version of the Management service is always HTTPs)
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if mgmTlsEnabled && config.ManagementURL.Port() == fmt.Sprintf("%d", ManagementLegacyPort) {
|
||||||
|
|
||||||
|
newURL, err := ParseURL("Management URL", fmt.Sprintf("%s://%s:%d",
|
||||||
|
config.ManagementURL.Scheme, config.ManagementURL.Hostname(), 443))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// here we check whether we could switch from the legacy 33073 port to the new 443
|
||||||
|
log.Infof("attempting to switch from the legacy Management URL %s to the new one %s",
|
||||||
|
config.ManagementURL.String(), newURL.String())
|
||||||
|
key, err := wgtypes.ParseKey(config.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("couldn't switch to the new Management %s", newURL.String())
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := mgm.NewClient(ctx, newURL.Host, key, mgmTlsEnabled)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("couldn't switch to the new Management %s", newURL.String())
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = client.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to close the Management service client %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// gRPC check
|
||||||
|
_, err = client.GetServerPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("couldn't switch to the new Management %s", newURL.String())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// everything is alright => update the config
|
||||||
|
newConfig, err := ReadConfig(newURL.String(), "", configPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("couldn't switch to the new Management %s", newURL.String())
|
||||||
|
return config, fmt.Errorf("failed updating config file: %v", err)
|
||||||
|
}
|
||||||
|
log.Infof("successfully switched to the new Management URL: %s", newURL.String())
|
||||||
|
|
||||||
|
return newConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||||
nbssh "github.com/netbirdio/netbird/client/ssh"
|
nbssh "github.com/netbirdio/netbird/client/ssh"
|
||||||
nbstatus "github.com/netbirdio/netbird/client/status"
|
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -99,6 +101,8 @@ type Engine struct {
|
|||||||
sshServer nbssh.Server
|
sshServer nbssh.Server
|
||||||
|
|
||||||
statusRecorder *nbstatus.Status
|
statusRecorder *nbstatus.Status
|
||||||
|
|
||||||
|
routeManager routemanager.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer is an instance of the Connection Peer
|
// Peer is an instance of the Connection Peer
|
||||||
@@ -182,6 +186,10 @@ func (e *Engine) Stop() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.routeManager != nil {
|
||||||
|
e.routeManager.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
log.Infof("stopped Netbird Engine")
|
log.Infof("stopped Netbird Engine")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -232,6 +240,8 @@ func (e *Engine) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder)
|
||||||
|
|
||||||
e.receiveSignalEvents()
|
e.receiveSignalEvents()
|
||||||
e.receiveManagementEvents()
|
e.receiveManagementEvents()
|
||||||
|
|
||||||
@@ -382,15 +392,14 @@ func signalCandidate(candidate ice.Candidate, myKey wgtypes.Key, remoteKey wgtyp
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed signaling candidate to the remote peer %s %s", remoteKey.String(), err)
|
|
||||||
// todo ??
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func signalAuth(uFrag string, pwd string, myKey wgtypes.Key, remoteKey wgtypes.Key, s signal.Client, isAnswer bool) error {
|
// SignalOfferAnswer signals either an offer or an answer to remote peer
|
||||||
|
func SignalOfferAnswer(offerAnswer peer.OfferAnswer, myKey wgtypes.Key, remoteKey wgtypes.Key, s signal.Client, isAnswer bool) error {
|
||||||
var t sProto.Body_Type
|
var t sProto.Body_Type
|
||||||
if isAnswer {
|
if isAnswer {
|
||||||
t = sProto.Body_ANSWER
|
t = sProto.Body_ANSWER
|
||||||
@@ -398,9 +407,9 @@ func signalAuth(uFrag string, pwd string, myKey wgtypes.Key, remoteKey wgtypes.K
|
|||||||
t = sProto.Body_OFFER
|
t = sProto.Body_OFFER
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := signal.MarshalCredential(myKey, remoteKey, &signal.Credential{
|
msg, err := signal.MarshalCredential(myKey, offerAnswer.WgListenPort, remoteKey, &signal.Credential{
|
||||||
UFrag: uFrag,
|
UFrag: offerAnswer.IceCredentials.UFrag,
|
||||||
Pwd: pwd,
|
Pwd: offerAnswer.IceCredentials.Pwd,
|
||||||
}, t)
|
}, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -620,11 +629,37 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
protoRoutes := networkMap.GetRoutes()
|
||||||
|
if protoRoutes == nil {
|
||||||
|
protoRoutes = []*mgmProto.Route{}
|
||||||
|
}
|
||||||
|
err := e.routeManager.UpdateRoutes(serial, toRoutes(protoRoutes))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to update routes, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
e.networkSerial = serial
|
e.networkSerial = serial
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toRoutes(protoRoutes []*mgmProto.Route) []*route.Route {
|
||||||
|
routes := make([]*route.Route, 0)
|
||||||
|
for _, protoRoute := range protoRoutes {
|
||||||
|
_, prefix, _ := route.ParseNetwork(protoRoute.Network)
|
||||||
|
convertedRoute := &route.Route{
|
||||||
|
ID: protoRoute.ID,
|
||||||
|
Network: prefix,
|
||||||
|
NetID: protoRoute.NetID,
|
||||||
|
NetworkType: route.NetworkType(protoRoute.NetworkType),
|
||||||
|
Peer: protoRoute.Peer,
|
||||||
|
Metric: int(protoRoute.Metric),
|
||||||
|
Masquerade: protoRoute.Masquerade,
|
||||||
|
}
|
||||||
|
routes = append(routes, convertedRoute)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
// addNewPeers adds peers that were not know before but arrived from the Management service with the update
|
// addNewPeers adds peers that were not know before but arrived from the Management service with the update
|
||||||
func (e *Engine) addNewPeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
|
func (e *Engine) addNewPeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
|
||||||
for _, p := range peersUpdate {
|
for _, p := range peersUpdate {
|
||||||
@@ -657,7 +692,7 @@ func (e *Engine) addNewPeer(peerConfig *mgmProto.RemotePeerConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e Engine) connWorker(conn *peer.Conn, peerKey string) {
|
func (e *Engine) connWorker(conn *peer.Conn, peerKey string) {
|
||||||
for {
|
for {
|
||||||
|
|
||||||
// randomize starting time a bit
|
// randomize starting time a bit
|
||||||
@@ -676,6 +711,13 @@ func (e Engine) connWorker(conn *peer.Conn, peerKey string) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we might have received new STUN and TURN servers meanwhile, so update them
|
||||||
|
e.syncMsgMux.Lock()
|
||||||
|
conf := conn.GetConf()
|
||||||
|
conf.StunTurn = append(e.STUNs, e.TURNs...)
|
||||||
|
conn.UpdateConf(conf)
|
||||||
|
e.syncMsgMux.Unlock()
|
||||||
|
|
||||||
err := conn.Open()
|
err := conn.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("connection to peer %s failed: %v", peerKey, err)
|
log.Debugf("connection to peer %s failed: %v", peerKey, err)
|
||||||
@@ -697,6 +739,7 @@ func (e Engine) peerExists(peerKey string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, error) {
|
func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, error) {
|
||||||
|
log.Debugf("creating peer connection %s", pubKey)
|
||||||
var stunTurn []*ice.URL
|
var stunTurn []*ice.URL
|
||||||
stunTurn = append(stunTurn, e.STUNs...)
|
stunTurn = append(stunTurn, e.STUNs...)
|
||||||
stunTurn = append(stunTurn, e.TURNs...)
|
stunTurn = append(stunTurn, e.TURNs...)
|
||||||
@@ -720,6 +763,7 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
|
|||||||
UDPMux: e.udpMux,
|
UDPMux: e.udpMux,
|
||||||
UDPMuxSrflx: e.udpMuxSrflx,
|
UDPMuxSrflx: e.udpMuxSrflx,
|
||||||
ProxyConfig: proxyConfig,
|
ProxyConfig: proxyConfig,
|
||||||
|
LocalWgPort: e.config.WgPort,
|
||||||
}
|
}
|
||||||
|
|
||||||
peerConn, err := peer.NewConn(config, e.statusRecorder)
|
peerConn, err := peer.NewConn(config, e.statusRecorder)
|
||||||
@@ -732,16 +776,16 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
signalOffer := func(uFrag string, pwd string) error {
|
signalOffer := func(offerAnswer peer.OfferAnswer) error {
|
||||||
return signalAuth(uFrag, pwd, e.config.WgPrivateKey, wgPubKey, e.signal, false)
|
return SignalOfferAnswer(offerAnswer, e.config.WgPrivateKey, wgPubKey, e.signal, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
signalCandidate := func(candidate ice.Candidate) error {
|
signalCandidate := func(candidate ice.Candidate) error {
|
||||||
return signalCandidate(candidate, e.config.WgPrivateKey, wgPubKey, e.signal)
|
return signalCandidate(candidate, e.config.WgPrivateKey, wgPubKey, e.signal)
|
||||||
}
|
}
|
||||||
|
|
||||||
signalAnswer := func(uFrag string, pwd string) error {
|
signalAnswer := func(offerAnswer peer.OfferAnswer) error {
|
||||||
return signalAuth(uFrag, pwd, e.config.WgPrivateKey, wgPubKey, e.signal, true)
|
return SignalOfferAnswer(offerAnswer, e.config.WgPrivateKey, wgPubKey, e.signal, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
peerConn.SetSignalCandidate(signalCandidate)
|
peerConn.SetSignalCandidate(signalCandidate)
|
||||||
@@ -770,18 +814,26 @@ func (e *Engine) receiveSignalEvents() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
conn.OnRemoteOffer(peer.IceCredentials{
|
conn.OnRemoteOffer(peer.OfferAnswer{
|
||||||
UFrag: remoteCred.UFrag,
|
IceCredentials: peer.IceCredentials{
|
||||||
Pwd: remoteCred.Pwd,
|
UFrag: remoteCred.UFrag,
|
||||||
|
Pwd: remoteCred.Pwd,
|
||||||
|
},
|
||||||
|
WgListenPort: int(msg.GetBody().GetWgListenPort()),
|
||||||
|
Version: msg.GetBody().GetNetBirdVersion(),
|
||||||
})
|
})
|
||||||
case sProto.Body_ANSWER:
|
case sProto.Body_ANSWER:
|
||||||
remoteCred, err := signal.UnMarshalCredential(msg)
|
remoteCred, err := signal.UnMarshalCredential(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
conn.OnRemoteAnswer(peer.IceCredentials{
|
conn.OnRemoteAnswer(peer.OfferAnswer{
|
||||||
UFrag: remoteCred.UFrag,
|
IceCredentials: peer.IceCredentials{
|
||||||
Pwd: remoteCred.Pwd,
|
UFrag: remoteCred.UFrag,
|
||||||
|
Pwd: remoteCred.Pwd,
|
||||||
|
},
|
||||||
|
WgListenPort: int(msg.GetBody().GetWgListenPort()),
|
||||||
|
Version: msg.GetBody().GetNetBirdVersion(),
|
||||||
})
|
})
|
||||||
case sProto.Body_CANDIDATE:
|
case sProto.Body_CANDIDATE:
|
||||||
candidate, err := ice.UnmarshalCandidate(msg.GetBody().Payload)
|
candidate, err := ice.UnmarshalCandidate(msg.GetBody().Payload)
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||||
"github.com/netbirdio/netbird/client/ssh"
|
"github.com/netbirdio/netbird/client/ssh"
|
||||||
nbstatus "github.com/netbirdio/netbird/client/status"
|
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -196,6 +199,7 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
|||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
}, nbstatus.NewRecorder())
|
}, nbstatus.NewRecorder())
|
||||||
engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", iface.DefaultMTU)
|
engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", iface.DefaultMTU)
|
||||||
|
engine.routeManager = routemanager.NewManager(ctx, key.PublicKey().String(), engine.wgInterface, engine.statusRecorder)
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
@@ -426,6 +430,142 @@ func TestEngine_Sync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
inputErr error
|
||||||
|
networkMap *mgmtProto.NetworkMap
|
||||||
|
expectedLen int
|
||||||
|
expectedRoutes []*route.Route
|
||||||
|
expectedSerial uint64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Routes Update Should Be Passed To Manager",
|
||||||
|
networkMap: &mgmtProto.NetworkMap{
|
||||||
|
Serial: 1,
|
||||||
|
PeerConfig: nil,
|
||||||
|
RemotePeersIsEmpty: false,
|
||||||
|
Routes: []*mgmtProto.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
Network: "192.168.0.0/24",
|
||||||
|
NetID: "n1",
|
||||||
|
Peer: "p1",
|
||||||
|
NetworkType: 1,
|
||||||
|
Masquerade: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
Network: "192.168.1.0/24",
|
||||||
|
NetID: "n2",
|
||||||
|
Peer: "p1",
|
||||||
|
NetworkType: 1,
|
||||||
|
Masquerade: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedLen: 2,
|
||||||
|
expectedRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
Network: netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
NetID: "n1",
|
||||||
|
Peer: "p1",
|
||||||
|
NetworkType: 1,
|
||||||
|
Masquerade: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
Network: netip.MustParsePrefix("192.168.1.0/24"),
|
||||||
|
NetID: "n2",
|
||||||
|
Peer: "p1",
|
||||||
|
NetworkType: 1,
|
||||||
|
Masquerade: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedSerial: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty Routes Update Should Be Passed",
|
||||||
|
networkMap: &mgmtProto.NetworkMap{
|
||||||
|
Serial: 1,
|
||||||
|
PeerConfig: nil,
|
||||||
|
RemotePeersIsEmpty: false,
|
||||||
|
Routes: nil,
|
||||||
|
},
|
||||||
|
expectedLen: 0,
|
||||||
|
expectedRoutes: []*route.Route{},
|
||||||
|
expectedSerial: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error Shouldn't Break Engine",
|
||||||
|
inputErr: fmt.Errorf("mocking error"),
|
||||||
|
networkMap: &mgmtProto.NetworkMap{
|
||||||
|
Serial: 1,
|
||||||
|
PeerConfig: nil,
|
||||||
|
RemotePeersIsEmpty: false,
|
||||||
|
Routes: nil,
|
||||||
|
},
|
||||||
|
expectedLen: 0,
|
||||||
|
expectedRoutes: []*route.Route{},
|
||||||
|
expectedSerial: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
// test setup
|
||||||
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
wgIfaceName := fmt.Sprintf("utun%d", 104+n)
|
||||||
|
wgAddr := fmt.Sprintf("100.66.%d.1/24", n)
|
||||||
|
|
||||||
|
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||||
|
WgIfaceName: wgIfaceName,
|
||||||
|
WgAddr: wgAddr,
|
||||||
|
WgPrivateKey: key,
|
||||||
|
WgPort: 33100,
|
||||||
|
}, nbstatus.NewRecorder())
|
||||||
|
engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, iface.DefaultMTU)
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
input := struct {
|
||||||
|
inputSerial uint64
|
||||||
|
inputRoutes []*route.Route
|
||||||
|
}{}
|
||||||
|
|
||||||
|
mockRouteManager := &routemanager.MockManager{
|
||||||
|
UpdateRoutesFunc: func(updateSerial uint64, newRoutes []*route.Route) error {
|
||||||
|
input.inputSerial = updateSerial
|
||||||
|
input.inputRoutes = newRoutes
|
||||||
|
return testCase.inputErr
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.routeManager = mockRouteManager
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
exitErr := engine.Stop()
|
||||||
|
if exitErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = engine.updateNetworkMap(testCase.networkMap)
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
assert.Equal(t, testCase.expectedSerial, input.inputSerial, "serial should match")
|
||||||
|
assert.Len(t, input.inputRoutes, testCase.expectedLen, "routes len should match")
|
||||||
|
assert.Equal(t, testCase.expectedRoutes, input.inputRoutes, "routes should match")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEngine_MultiplePeers(t *testing.T) {
|
func TestEngine_MultiplePeers(t *testing.T) {
|
||||||
// log.SetLevel(log.DebugLevel)
|
// log.SetLevel(log.DebugLevel)
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,22 @@ func Login(ctx context.Context, config *Config, setupKey string, jwtToken string
|
|||||||
mgmTlsEnabled = true
|
mgmTlsEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("connecting to Management Service %s", config.ManagementURL.String())
|
log.Debugf("connecting to the Management service %s", config.ManagementURL.String())
|
||||||
mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
|
log.Errorf("failed connecting to the Management service %s %v", config.ManagementURL.String(), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Debugf("connected to management Service %s", config.ManagementURL.String())
|
log.Debugf("connected to the Management service %s", config.ManagementURL.String())
|
||||||
|
defer func() {
|
||||||
|
err = mgmClient.Close()
|
||||||
|
if err != nil {
|
||||||
|
cStatus, ok := status.FromError(err)
|
||||||
|
if !ok || ok && cStatus.Code() != codes.Canceled {
|
||||||
|
log.Warnf("failed to close the Management service client, err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
serverKey, err := mgmClient.GetServerPublicKey()
|
serverKey, err := mgmClient.GetServerPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -49,10 +58,11 @@ func Login(ctx context.Context, config *Config, setupKey string, jwtToken string
|
|||||||
log.Errorf("failed logging-in peer on Management Service : %v", err)
|
log.Errorf("failed logging-in peer on Management Service : %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
log.Infof("peer has successfully logged-in to the Management service %s", config.ManagementURL.String())
|
||||||
|
|
||||||
err = mgmClient.Close()
|
err = mgmClient.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed closing Management Service client: %v", err)
|
log.Errorf("failed to close the Management service client: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,8 +82,6 @@ func loginPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.Grp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("peer has successfully logged-in to Management Service")
|
|
||||||
|
|
||||||
return loginResp, nil
|
return loginResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -14,7 +16,6 @@ import (
|
|||||||
// OAuthClient is a OAuth client interface for various idp providers
|
// OAuthClient is a OAuth client interface for various idp providers
|
||||||
type OAuthClient interface {
|
type OAuthClient interface {
|
||||||
RequestDeviceCode(ctx context.Context) (DeviceAuthInfo, error)
|
RequestDeviceCode(ctx context.Context) (DeviceAuthInfo, error)
|
||||||
RotateAccessToken(ctx context.Context, refreshToken string) (TokenInfo, error)
|
|
||||||
WaitToken(ctx context.Context, info DeviceAuthInfo) (TokenInfo, error)
|
WaitToken(ctx context.Context, info DeviceAuthInfo) (TokenInfo, error)
|
||||||
GetClientID(ctx context.Context) string
|
GetClientID(ctx context.Context) string
|
||||||
}
|
}
|
||||||
@@ -55,8 +56,10 @@ type Hosted struct {
|
|||||||
Audience string
|
Audience string
|
||||||
// Hosted Native application client id
|
// Hosted Native application client id
|
||||||
ClientID string
|
ClientID string
|
||||||
// Hosted domain
|
// TokenEndpoint to request access token
|
||||||
Domain string
|
TokenEndpoint string
|
||||||
|
// DeviceAuthEndpoint to request device authorization code
|
||||||
|
DeviceAuthEndpoint string
|
||||||
|
|
||||||
HTTPClient HTTPClient
|
HTTPClient HTTPClient
|
||||||
}
|
}
|
||||||
@@ -84,11 +87,11 @@ type TokenRequestResponse struct {
|
|||||||
|
|
||||||
// Claims used when validating the access token
|
// Claims used when validating the access token
|
||||||
type Claims struct {
|
type Claims struct {
|
||||||
Audience string `json:"aud"`
|
Audience interface{} `json:"aud"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHostedDeviceFlow returns an Hosted OAuth client
|
// NewHostedDeviceFlow returns an Hosted OAuth client
|
||||||
func NewHostedDeviceFlow(audience string, clientID string, domain string) *Hosted {
|
func NewHostedDeviceFlow(audience string, clientID string, tokenEndpoint string, deviceAuthEndpoint string) *Hosted {
|
||||||
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
httpTransport.MaxIdleConns = 5
|
httpTransport.MaxIdleConns = 5
|
||||||
|
|
||||||
@@ -98,10 +101,11 @@ func NewHostedDeviceFlow(audience string, clientID string, domain string) *Hoste
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Hosted{
|
return &Hosted{
|
||||||
Audience: audience,
|
Audience: audience,
|
||||||
ClientID: clientID,
|
ClientID: clientID,
|
||||||
Domain: domain,
|
TokenEndpoint: tokenEndpoint,
|
||||||
HTTPClient: httpClient,
|
HTTPClient: httpClient,
|
||||||
|
DeviceAuthEndpoint: deviceAuthEndpoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,22 +116,15 @@ func (h *Hosted) GetClientID(ctx context.Context) string {
|
|||||||
|
|
||||||
// RequestDeviceCode requests a device code login flow information from Hosted
|
// RequestDeviceCode requests a device code login flow information from Hosted
|
||||||
func (h *Hosted) RequestDeviceCode(ctx context.Context) (DeviceAuthInfo, error) {
|
func (h *Hosted) RequestDeviceCode(ctx context.Context) (DeviceAuthInfo, error) {
|
||||||
url := "https://" + h.Domain + "/oauth/device/code"
|
form := url.Values{}
|
||||||
codePayload := RequestDeviceCodePayload{
|
form.Add("client_id", h.ClientID)
|
||||||
Audience: h.Audience,
|
form.Add("audience", h.Audience)
|
||||||
ClientID: h.ClientID,
|
req, err := http.NewRequest("POST", h.DeviceAuthEndpoint,
|
||||||
}
|
strings.NewReader(form.Encode()))
|
||||||
p, err := json.Marshal(codePayload)
|
|
||||||
if err != nil {
|
|
||||||
return DeviceAuthInfo{}, fmt.Errorf("parsing payload failed with error: %v", err)
|
|
||||||
}
|
|
||||||
payload := strings.NewReader(string(p))
|
|
||||||
req, err := http.NewRequest("POST", url, payload)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DeviceAuthInfo{}, fmt.Errorf("creating request failed with error: %v", err)
|
return DeviceAuthInfo{}, fmt.Errorf("creating request failed with error: %v", err)
|
||||||
}
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
req.Header.Add("content-type", "application/json")
|
|
||||||
|
|
||||||
res, err := h.HTTPClient.Do(req)
|
res, err := h.HTTPClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -135,7 +132,7 @@ func (h *Hosted) RequestDeviceCode(ctx context.Context) (DeviceAuthInfo, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DeviceAuthInfo{}, fmt.Errorf("reading body failed with error: %v", err)
|
return DeviceAuthInfo{}, fmt.Errorf("reading body failed with error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -153,6 +150,48 @@ func (h *Hosted) RequestDeviceCode(ctx context.Context) (DeviceAuthInfo, error)
|
|||||||
return deviceCode, err
|
return deviceCode, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Hosted) requestToken(info DeviceAuthInfo) (TokenRequestResponse, error) {
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("client_id", h.ClientID)
|
||||||
|
form.Add("grant_type", HostedGrantType)
|
||||||
|
form.Add("device_code", info.DeviceCode)
|
||||||
|
req, err := http.NewRequest("POST", h.TokenEndpoint, strings.NewReader(form.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return TokenRequestResponse{}, fmt.Errorf("failed to create request access token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
res, err := h.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return TokenRequestResponse{}, fmt.Errorf("failed to request access token with error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return TokenRequestResponse{}, fmt.Errorf("failed reading access token response body with error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode > 499 {
|
||||||
|
return TokenRequestResponse{}, fmt.Errorf("access token response returned code: %s", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenResponse := TokenRequestResponse{}
|
||||||
|
err = json.Unmarshal(body, &tokenResponse)
|
||||||
|
if err != nil {
|
||||||
|
return TokenRequestResponse{}, fmt.Errorf("parsing token response failed with error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
// WaitToken waits user's login and authorize the app. Once the user's authorize
|
// WaitToken waits user's login and authorize the app. Once the user's authorize
|
||||||
// it retrieves the access token from Hosted's endpoint and validates it before returning
|
// it retrieves the access token from Hosted's endpoint and validates it before returning
|
||||||
func (h *Hosted) WaitToken(ctx context.Context, info DeviceAuthInfo) (TokenInfo, error) {
|
func (h *Hosted) WaitToken(ctx context.Context, info DeviceAuthInfo) (TokenInfo, error) {
|
||||||
@@ -163,24 +202,8 @@ func (h *Hosted) WaitToken(ctx context.Context, info DeviceAuthInfo) (TokenInfo,
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return TokenInfo{}, ctx.Err()
|
return TokenInfo{}, ctx.Err()
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
url := "https://" + h.Domain + "/oauth/token"
|
|
||||||
tokenReqPayload := TokenRequestPayload{
|
|
||||||
GrantType: HostedGrantType,
|
|
||||||
DeviceCode: info.DeviceCode,
|
|
||||||
ClientID: h.ClientID,
|
|
||||||
}
|
|
||||||
|
|
||||||
body, statusCode, err := requestToken(h.HTTPClient, url, tokenReqPayload)
|
tokenResponse, err := h.requestToken(info)
|
||||||
if err != nil {
|
|
||||||
return TokenInfo{}, fmt.Errorf("wait for token: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if statusCode > 499 {
|
|
||||||
return TokenInfo{}, fmt.Errorf("wait token code returned error: %s", string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenResponse := TokenRequestResponse{}
|
|
||||||
err = json.Unmarshal(body, &tokenResponse)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TokenInfo{}, fmt.Errorf("parsing token response failed with error: %v", err)
|
return TokenInfo{}, fmt.Errorf("parsing token response failed with error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -214,71 +237,6 @@ func (h *Hosted) WaitToken(ctx context.Context, info DeviceAuthInfo) (TokenInfo,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RotateAccessToken requests a new token using an existing refresh token
|
|
||||||
func (h *Hosted) RotateAccessToken(ctx context.Context, refreshToken string) (TokenInfo, error) {
|
|
||||||
url := "https://" + h.Domain + "/oauth/token"
|
|
||||||
tokenReqPayload := TokenRequestPayload{
|
|
||||||
GrantType: HostedRefreshGrant,
|
|
||||||
ClientID: h.ClientID,
|
|
||||||
RefreshToken: refreshToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
body, statusCode, err := requestToken(h.HTTPClient, url, tokenReqPayload)
|
|
||||||
if err != nil {
|
|
||||||
return TokenInfo{}, fmt.Errorf("rotate access token: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if statusCode != 200 {
|
|
||||||
return TokenInfo{}, fmt.Errorf("rotating token returned error: %s", string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenResponse := TokenRequestResponse{}
|
|
||||||
err = json.Unmarshal(body, &tokenResponse)
|
|
||||||
if err != nil {
|
|
||||||
return TokenInfo{}, fmt.Errorf("parsing token response failed with error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = isValidAccessToken(tokenResponse.AccessToken, h.Audience)
|
|
||||||
if err != nil {
|
|
||||||
return TokenInfo{}, fmt.Errorf("validate access token failed with error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenInfo := TokenInfo{
|
|
||||||
AccessToken: tokenResponse.AccessToken,
|
|
||||||
TokenType: tokenResponse.TokenType,
|
|
||||||
RefreshToken: tokenResponse.RefreshToken,
|
|
||||||
IDToken: tokenResponse.IDToken,
|
|
||||||
ExpiresIn: tokenResponse.ExpiresIn,
|
|
||||||
}
|
|
||||||
return tokenInfo, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestToken(client HTTPClient, url string, tokenReqPayload TokenRequestPayload) ([]byte, int, error) {
|
|
||||||
p, err := json.Marshal(tokenReqPayload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, fmt.Errorf("parsing token payload failed with error: %v", err)
|
|
||||||
}
|
|
||||||
payload := strings.NewReader(string(p))
|
|
||||||
req, err := http.NewRequest("POST", url, payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, fmt.Errorf("creating token request failed with error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("content-type", "application/json")
|
|
||||||
|
|
||||||
res, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, fmt.Errorf("doing token request failed with error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer res.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, fmt.Errorf("reading token body failed with error: %v", err)
|
|
||||||
}
|
|
||||||
return body, res.StatusCode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isValidAccessToken is a simple validation of the access token
|
// isValidAccessToken is a simple validation of the access token
|
||||||
func isValidAccessToken(token string, audience string) error {
|
func isValidAccessToken(token string, audience string) error {
|
||||||
if token == "" {
|
if token == "" {
|
||||||
@@ -297,9 +255,24 @@ func isValidAccessToken(token string, audience string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if claims.Audience != audience {
|
if claims.Audience == nil {
|
||||||
return fmt.Errorf("invalid audience")
|
return fmt.Errorf("required token field audience is absent")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// Audience claim of JWT can be a string or an array of strings
|
||||||
|
typ := reflect.TypeOf(claims.Audience)
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
if claims.Audience == audience {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
for _, aud := range claims.Audience.([]interface{}) {
|
||||||
|
if audience == aud {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("invalid JWT token audience field")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -24,7 +24,7 @@ type mockHTTPClient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
func (c *mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
body, err := ioutil.ReadAll(req.Body)
|
body, err := io.ReadAll(req.Body)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.reqBody = string(body)
|
c.reqBody = string(body)
|
||||||
}
|
}
|
||||||
@@ -33,13 +33,13 @@ func (c *mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
|||||||
c.count++
|
c.count++
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: c.code,
|
StatusCode: c.code,
|
||||||
Body: ioutil.NopCloser(strings.NewReader(c.countResBody)),
|
Body: io.NopCloser(strings.NewReader(c.countResBody)),
|
||||||
}, c.err
|
}, c.err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: c.code,
|
StatusCode: c.code,
|
||||||
Body: ioutil.NopCloser(strings.NewReader(c.resBody)),
|
Body: io.NopCloser(strings.NewReader(c.resBody)),
|
||||||
}, c.err
|
}, c.err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,15 +54,19 @@ func TestHosted_RequestDeviceCode(t *testing.T) {
|
|||||||
testingFunc require.ComparisonAssertionFunc
|
testingFunc require.ComparisonAssertionFunc
|
||||||
expectedOut DeviceAuthInfo
|
expectedOut DeviceAuthInfo
|
||||||
expectedMSG string
|
expectedMSG string
|
||||||
expectPayload RequestDeviceCodePayload
|
expectPayload string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expectedAudience := "ok"
|
||||||
|
expectedClientID := "bla"
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("audience", expectedAudience)
|
||||||
|
form.Add("client_id", expectedClientID)
|
||||||
|
expectPayload := form.Encode()
|
||||||
|
|
||||||
testCase1 := test{
|
testCase1 := test{
|
||||||
name: "Payload Is Valid",
|
name: "Payload Is Valid",
|
||||||
expectPayload: RequestDeviceCodePayload{
|
expectPayload: expectPayload,
|
||||||
Audience: "ok",
|
|
||||||
ClientID: "bla",
|
|
||||||
},
|
|
||||||
inputReqCode: 200,
|
inputReqCode: 200,
|
||||||
testingErrFunc: require.Error,
|
testingErrFunc: require.Error,
|
||||||
testingFunc: require.EqualValues,
|
testingFunc: require.EqualValues,
|
||||||
@@ -74,6 +78,7 @@ func TestHosted_RequestDeviceCode(t *testing.T) {
|
|||||||
testingErrFunc: require.Error,
|
testingErrFunc: require.Error,
|
||||||
expectedErrorMSG: "should return error",
|
expectedErrorMSG: "should return error",
|
||||||
testingFunc: require.EqualValues,
|
testingFunc: require.EqualValues,
|
||||||
|
expectPayload: expectPayload,
|
||||||
}
|
}
|
||||||
|
|
||||||
testCase3 := test{
|
testCase3 := test{
|
||||||
@@ -82,15 +87,13 @@ func TestHosted_RequestDeviceCode(t *testing.T) {
|
|||||||
testingErrFunc: require.Error,
|
testingErrFunc: require.Error,
|
||||||
expectedErrorMSG: "should return error",
|
expectedErrorMSG: "should return error",
|
||||||
testingFunc: require.EqualValues,
|
testingFunc: require.EqualValues,
|
||||||
|
expectPayload: expectPayload,
|
||||||
}
|
}
|
||||||
testCase4Out := DeviceAuthInfo{ExpiresIn: 10}
|
testCase4Out := DeviceAuthInfo{ExpiresIn: 10}
|
||||||
testCase4 := test{
|
testCase4 := test{
|
||||||
name: "Got Device Code",
|
name: "Got Device Code",
|
||||||
inputResBody: fmt.Sprintf("{\"expires_in\":%d}", testCase4Out.ExpiresIn),
|
inputResBody: fmt.Sprintf("{\"expires_in\":%d}", testCase4Out.ExpiresIn),
|
||||||
expectPayload: RequestDeviceCodePayload{
|
expectPayload: expectPayload,
|
||||||
Audience: "ok",
|
|
||||||
ClientID: "bla",
|
|
||||||
},
|
|
||||||
inputReqCode: 200,
|
inputReqCode: 200,
|
||||||
testingErrFunc: require.NoError,
|
testingErrFunc: require.NoError,
|
||||||
testingFunc: require.EqualValues,
|
testingFunc: require.EqualValues,
|
||||||
@@ -108,18 +111,17 @@ func TestHosted_RequestDeviceCode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hosted := Hosted{
|
hosted := Hosted{
|
||||||
Audience: testCase.expectPayload.Audience,
|
Audience: expectedAudience,
|
||||||
ClientID: testCase.expectPayload.ClientID,
|
ClientID: expectedClientID,
|
||||||
Domain: "test.hosted.com",
|
TokenEndpoint: "test.hosted.com/token",
|
||||||
HTTPClient: &httpClient,
|
DeviceAuthEndpoint: "test.hosted.com/device/auth",
|
||||||
|
HTTPClient: &httpClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
authInfo, err := hosted.RequestDeviceCode(context.TODO())
|
authInfo, err := hosted.RequestDeviceCode(context.TODO())
|
||||||
testCase.testingErrFunc(t, err, testCase.expectedErrorMSG)
|
testCase.testingErrFunc(t, err, testCase.expectedErrorMSG)
|
||||||
|
|
||||||
payload, _ := json.Marshal(testCase.expectPayload)
|
require.EqualValues(t, expectPayload, httpClient.reqBody, "payload should match")
|
||||||
|
|
||||||
require.EqualValues(t, string(payload), httpClient.reqBody, "payload should match")
|
|
||||||
|
|
||||||
testCase.testingFunc(t, testCase.expectedOut, authInfo, testCase.expectedMSG)
|
testCase.testingFunc(t, testCase.expectedOut, authInfo, testCase.expectedMSG)
|
||||||
|
|
||||||
@@ -143,7 +145,7 @@ func TestHosted_WaitToken(t *testing.T) {
|
|||||||
testingFunc require.ComparisonAssertionFunc
|
testingFunc require.ComparisonAssertionFunc
|
||||||
expectedOut TokenInfo
|
expectedOut TokenInfo
|
||||||
expectedMSG string
|
expectedMSG string
|
||||||
expectPayload TokenRequestPayload
|
expectPayload string
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultInfo := DeviceAuthInfo{
|
defaultInfo := DeviceAuthInfo{
|
||||||
@@ -152,11 +154,13 @@ func TestHosted_WaitToken(t *testing.T) {
|
|||||||
Interval: 1,
|
Interval: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenReqPayload := TokenRequestPayload{
|
clientID := "test"
|
||||||
GrantType: HostedGrantType,
|
|
||||||
DeviceCode: defaultInfo.DeviceCode,
|
form := url.Values{}
|
||||||
ClientID: "test",
|
form.Add("grant_type", HostedGrantType)
|
||||||
}
|
form.Add("device_code", defaultInfo.DeviceCode)
|
||||||
|
form.Add("client_id", clientID)
|
||||||
|
tokenReqPayload := form.Encode()
|
||||||
|
|
||||||
testCase1 := test{
|
testCase1 := test{
|
||||||
name: "Payload Is Valid",
|
name: "Payload Is Valid",
|
||||||
@@ -268,10 +272,11 @@ func TestHosted_WaitToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hosted := Hosted{
|
hosted := Hosted{
|
||||||
Audience: testCase.inputAudience,
|
Audience: testCase.inputAudience,
|
||||||
ClientID: testCase.expectPayload.ClientID,
|
ClientID: clientID,
|
||||||
Domain: "test.hosted.com",
|
TokenEndpoint: "test.hosted.com/token",
|
||||||
HTTPClient: &httpClient,
|
DeviceAuthEndpoint: "test.hosted.com/device/auth",
|
||||||
|
HTTPClient: &httpClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.TODO(), testCase.inputTimeout)
|
ctx, cancel := context.WithTimeout(context.TODO(), testCase.inputTimeout)
|
||||||
@@ -279,12 +284,7 @@ func TestHosted_WaitToken(t *testing.T) {
|
|||||||
tokenInfo, err := hosted.WaitToken(ctx, testCase.inputInfo)
|
tokenInfo, err := hosted.WaitToken(ctx, testCase.inputInfo)
|
||||||
testCase.testingErrFunc(t, err, testCase.expectedErrorMSG)
|
testCase.testingErrFunc(t, err, testCase.expectedErrorMSG)
|
||||||
|
|
||||||
var payload []byte
|
require.EqualValues(t, testCase.expectPayload, httpClient.reqBody, "payload should match")
|
||||||
var emptyPayload TokenRequestPayload
|
|
||||||
if testCase.expectPayload != emptyPayload {
|
|
||||||
payload, _ = json.Marshal(testCase.expectPayload)
|
|
||||||
}
|
|
||||||
require.EqualValues(t, string(payload), httpClient.reqBody, "payload should match")
|
|
||||||
|
|
||||||
testCase.testingFunc(t, testCase.expectedOut, tokenInfo, testCase.expectedMSG)
|
testCase.testingFunc(t, testCase.expectedOut, tokenInfo, testCase.expectedMSG)
|
||||||
|
|
||||||
@@ -293,123 +293,3 @@ func TestHosted_WaitToken(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHosted_RotateAccessToken(t *testing.T) {
|
|
||||||
type test struct {
|
|
||||||
name string
|
|
||||||
inputResBody string
|
|
||||||
inputReqCode int
|
|
||||||
inputReqError error
|
|
||||||
inputMaxReqs int
|
|
||||||
inputInfo DeviceAuthInfo
|
|
||||||
inputAudience string
|
|
||||||
testingErrFunc require.ErrorAssertionFunc
|
|
||||||
expectedErrorMSG string
|
|
||||||
testingFunc require.ComparisonAssertionFunc
|
|
||||||
expectedOut TokenInfo
|
|
||||||
expectedMSG string
|
|
||||||
expectPayload TokenRequestPayload
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultInfo := DeviceAuthInfo{
|
|
||||||
DeviceCode: "test",
|
|
||||||
ExpiresIn: 10,
|
|
||||||
Interval: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenReqPayload := TokenRequestPayload{
|
|
||||||
GrantType: HostedRefreshGrant,
|
|
||||||
ClientID: "test",
|
|
||||||
RefreshToken: "refresh_test",
|
|
||||||
}
|
|
||||||
|
|
||||||
testCase1 := test{
|
|
||||||
name: "Payload Is Valid",
|
|
||||||
inputInfo: defaultInfo,
|
|
||||||
inputReqCode: 200,
|
|
||||||
testingErrFunc: require.Error,
|
|
||||||
testingFunc: require.EqualValues,
|
|
||||||
expectPayload: tokenReqPayload,
|
|
||||||
}
|
|
||||||
|
|
||||||
testCase2 := test{
|
|
||||||
name: "Exit On Network Error",
|
|
||||||
inputInfo: defaultInfo,
|
|
||||||
expectPayload: tokenReqPayload,
|
|
||||||
inputReqError: fmt.Errorf("error"),
|
|
||||||
testingErrFunc: require.Error,
|
|
||||||
expectedErrorMSG: "should return error",
|
|
||||||
testingFunc: require.EqualValues,
|
|
||||||
}
|
|
||||||
|
|
||||||
testCase3 := test{
|
|
||||||
name: "Exit On Non 200 Status Code",
|
|
||||||
inputInfo: defaultInfo,
|
|
||||||
inputReqCode: 401,
|
|
||||||
expectPayload: tokenReqPayload,
|
|
||||||
testingErrFunc: require.Error,
|
|
||||||
expectedErrorMSG: "should return error",
|
|
||||||
testingFunc: require.EqualValues,
|
|
||||||
}
|
|
||||||
|
|
||||||
audience := "test"
|
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{"aud": audience})
|
|
||||||
var hmacSampleSecret []byte
|
|
||||||
tokenString, _ := token.SignedString(hmacSampleSecret)
|
|
||||||
|
|
||||||
testCase4 := test{
|
|
||||||
name: "Exit On Invalid Audience",
|
|
||||||
inputInfo: defaultInfo,
|
|
||||||
inputResBody: fmt.Sprintf("{\"access_token\":\"%s\"}", tokenString),
|
|
||||||
inputReqCode: 200,
|
|
||||||
inputAudience: "super test",
|
|
||||||
testingErrFunc: require.Error,
|
|
||||||
testingFunc: require.EqualValues,
|
|
||||||
expectPayload: tokenReqPayload,
|
|
||||||
}
|
|
||||||
|
|
||||||
testCase5 := test{
|
|
||||||
name: "Received Token Info",
|
|
||||||
inputInfo: defaultInfo,
|
|
||||||
inputResBody: fmt.Sprintf("{\"access_token\":\"%s\"}", tokenString),
|
|
||||||
inputReqCode: 200,
|
|
||||||
inputAudience: audience,
|
|
||||||
testingErrFunc: require.NoError,
|
|
||||||
testingFunc: require.EqualValues,
|
|
||||||
expectPayload: tokenReqPayload,
|
|
||||||
expectedOut: TokenInfo{AccessToken: tokenString},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range []test{testCase1, testCase2, testCase3, testCase4, testCase5} {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
|
|
||||||
httpClient := mockHTTPClient{
|
|
||||||
resBody: testCase.inputResBody,
|
|
||||||
code: testCase.inputReqCode,
|
|
||||||
err: testCase.inputReqError,
|
|
||||||
MaxReqs: testCase.inputMaxReqs,
|
|
||||||
}
|
|
||||||
|
|
||||||
hosted := Hosted{
|
|
||||||
Audience: testCase.inputAudience,
|
|
||||||
ClientID: testCase.expectPayload.ClientID,
|
|
||||||
Domain: "test.hosted.com",
|
|
||||||
HTTPClient: &httpClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenInfo, err := hosted.RotateAccessToken(context.TODO(), testCase.expectPayload.RefreshToken)
|
|
||||||
testCase.testingErrFunc(t, err, testCase.expectedErrorMSG)
|
|
||||||
|
|
||||||
var payload []byte
|
|
||||||
var emptyPayload TokenRequestPayload
|
|
||||||
if testCase.expectPayload != emptyPayload {
|
|
||||||
payload, _ = json.Marshal(testCase.expectPayload)
|
|
||||||
}
|
|
||||||
require.EqualValues(t, string(payload), httpClient.reqBody, "payload should match")
|
|
||||||
|
|
||||||
testCase.testingFunc(t, testCase.expectedOut, tokenInfo, testCase.expectedMSG)
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package peer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
nbStatus "github.com/netbirdio/netbird/client/status"
|
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||||
|
"github.com/netbirdio/netbird/client/system"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl"
|
"golang.zx2c4.com/wireguard/wgctrl"
|
||||||
"net"
|
"net"
|
||||||
@@ -36,6 +37,20 @@ type ConnConfig struct {
|
|||||||
|
|
||||||
UDPMux ice.UDPMux
|
UDPMux ice.UDPMux
|
||||||
UDPMuxSrflx ice.UniversalUDPMux
|
UDPMuxSrflx ice.UniversalUDPMux
|
||||||
|
|
||||||
|
LocalWgPort int
|
||||||
|
}
|
||||||
|
|
||||||
|
// OfferAnswer represents a session establishment offer or answer
|
||||||
|
type OfferAnswer struct {
|
||||||
|
IceCredentials IceCredentials
|
||||||
|
// WgListenPort is a remote WireGuard listen port.
|
||||||
|
// This field is used when establishing a direct WireGuard connection without any proxy.
|
||||||
|
// We can set the remote peer's endpoint with this port.
|
||||||
|
WgListenPort int
|
||||||
|
|
||||||
|
// Version of NetBird Agent
|
||||||
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
// IceCredentials ICE protocol credentials struct
|
// IceCredentials ICE protocol credentials struct
|
||||||
@@ -51,13 +66,13 @@ type Conn struct {
|
|||||||
// signalCandidate is a handler function to signal remote peer about local connection candidate
|
// signalCandidate is a handler function to signal remote peer about local connection candidate
|
||||||
signalCandidate func(candidate ice.Candidate) error
|
signalCandidate func(candidate ice.Candidate) error
|
||||||
// signalOffer is a handler function to signal remote peer our connection offer (credentials)
|
// signalOffer is a handler function to signal remote peer our connection offer (credentials)
|
||||||
signalOffer func(uFrag string, pwd string) error
|
signalOffer func(OfferAnswer) error
|
||||||
signalAnswer func(uFrag string, pwd string) error
|
signalAnswer func(OfferAnswer) error
|
||||||
|
|
||||||
// remoteOffersCh is a channel used to wait for remote credentials to proceed with the connection
|
// remoteOffersCh is a channel used to wait for remote credentials to proceed with the connection
|
||||||
remoteOffersCh chan IceCredentials
|
remoteOffersCh chan OfferAnswer
|
||||||
// remoteAnswerCh is a channel used to wait for remote credentials answer (confirmation of our offer) to proceed with the connection
|
// remoteAnswerCh is a channel used to wait for remote credentials answer (confirmation of our offer) to proceed with the connection
|
||||||
remoteAnswerCh chan IceCredentials
|
remoteAnswerCh chan OfferAnswer
|
||||||
closeCh chan struct{}
|
closeCh chan struct{}
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
notifyDisconnected context.CancelFunc
|
notifyDisconnected context.CancelFunc
|
||||||
@@ -75,6 +90,11 @@ func (conn *Conn) GetConf() ConnConfig {
|
|||||||
return conn.config
|
return conn.config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateConf updates the connection config
|
||||||
|
func (conn *Conn) UpdateConf(conf ConnConfig) {
|
||||||
|
conn.config = conf
|
||||||
|
}
|
||||||
|
|
||||||
// NewConn creates a new not opened Conn to the remote peer.
|
// NewConn creates a new not opened Conn to the remote peer.
|
||||||
// To establish a connection run Conn.Open
|
// To establish a connection run Conn.Open
|
||||||
func NewConn(config ConnConfig, statusRecorder *nbStatus.Status) (*Conn, error) {
|
func NewConn(config ConnConfig, statusRecorder *nbStatus.Status) (*Conn, error) {
|
||||||
@@ -83,8 +103,8 @@ func NewConn(config ConnConfig, statusRecorder *nbStatus.Status) (*Conn, error)
|
|||||||
mu: sync.Mutex{},
|
mu: sync.Mutex{},
|
||||||
status: StatusDisconnected,
|
status: StatusDisconnected,
|
||||||
closeCh: make(chan struct{}),
|
closeCh: make(chan struct{}),
|
||||||
remoteOffersCh: make(chan IceCredentials),
|
remoteOffersCh: make(chan OfferAnswer),
|
||||||
remoteAnswerCh: make(chan IceCredentials),
|
remoteAnswerCh: make(chan OfferAnswer),
|
||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -195,15 +215,15 @@ func (conn *Conn) Open() error {
|
|||||||
// Only continue once we got a connection confirmation from the remote peer.
|
// Only continue once we got a connection confirmation from the remote peer.
|
||||||
// The connection timeout could have happened before a confirmation received from the remote.
|
// The connection timeout could have happened before a confirmation received from the remote.
|
||||||
// The connection could have also been closed externally (e.g. when we received an update from the management that peer shouldn't be connected)
|
// The connection could have also been closed externally (e.g. when we received an update from the management that peer shouldn't be connected)
|
||||||
var remoteCredentials IceCredentials
|
var remoteOfferAnswer OfferAnswer
|
||||||
select {
|
select {
|
||||||
case remoteCredentials = <-conn.remoteOffersCh:
|
case remoteOfferAnswer = <-conn.remoteOffersCh:
|
||||||
// received confirmation from the remote peer -> ready to proceed
|
// received confirmation from the remote peer -> ready to proceed
|
||||||
err = conn.sendAnswer()
|
err = conn.sendAnswer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case remoteCredentials = <-conn.remoteAnswerCh:
|
case remoteOfferAnswer = <-conn.remoteAnswerCh:
|
||||||
case <-time.After(conn.config.Timeout):
|
case <-time.After(conn.config.Timeout):
|
||||||
return NewConnectionTimeoutError(conn.config.Key, conn.config.Timeout)
|
return NewConnectionTimeoutError(conn.config.Key, conn.config.Timeout)
|
||||||
case <-conn.closeCh:
|
case <-conn.closeCh:
|
||||||
@@ -211,7 +231,8 @@ func (conn *Conn) Open() error {
|
|||||||
return NewConnectionClosedError(conn.config.Key)
|
return NewConnectionClosedError(conn.config.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("received connection confirmation from peer %s", conn.config.Key)
|
log.Debugf("received connection confirmation from peer %s running version %s and with remote WireGuard listen port %d",
|
||||||
|
conn.config.Key, remoteOfferAnswer.Version, remoteOfferAnswer.WgListenPort)
|
||||||
|
|
||||||
// at this point we received offer/answer and we are ready to gather candidates
|
// at this point we received offer/answer and we are ready to gather candidates
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
@@ -240,16 +261,21 @@ func (conn *Conn) Open() error {
|
|||||||
isControlling := conn.config.LocalKey > conn.config.Key
|
isControlling := conn.config.LocalKey > conn.config.Key
|
||||||
var remoteConn *ice.Conn
|
var remoteConn *ice.Conn
|
||||||
if isControlling {
|
if isControlling {
|
||||||
remoteConn, err = conn.agent.Dial(conn.ctx, remoteCredentials.UFrag, remoteCredentials.Pwd)
|
remoteConn, err = conn.agent.Dial(conn.ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd)
|
||||||
} else {
|
} else {
|
||||||
remoteConn, err = conn.agent.Accept(conn.ctx, remoteCredentials.UFrag, remoteCredentials.Pwd)
|
remoteConn, err = conn.agent.Accept(conn.ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dynamically set remote WireGuard port is other side specified a different one from the default one
|
||||||
|
remoteWgPort := iface.DefaultWgPort
|
||||||
|
if remoteOfferAnswer.WgListenPort != 0 {
|
||||||
|
remoteWgPort = remoteOfferAnswer.WgListenPort
|
||||||
|
}
|
||||||
// the ice connection has been established successfully so we are ready to start the proxy
|
// the ice connection has been established successfully so we are ready to start the proxy
|
||||||
err = conn.startProxy(remoteConn)
|
err = conn.startProxy(remoteConn, remoteWgPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -283,6 +309,10 @@ func shouldUseProxy(pair *ice.CandidatePair) bool {
|
|||||||
remoteIsPublic := IsPublicIP(remoteIP)
|
remoteIsPublic := IsPublicIP(remoteIP)
|
||||||
myIsPublic := IsPublicIP(myIp)
|
myIsPublic := IsPublicIP(myIp)
|
||||||
|
|
||||||
|
if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
//one of the hosts has a public IP
|
//one of the hosts has a public IP
|
||||||
if remoteIsPublic && pair.Remote.Type() == ice.CandidateTypeHost {
|
if remoteIsPublic && pair.Remote.Type() == ice.CandidateTypeHost {
|
||||||
return false
|
return false
|
||||||
@@ -310,7 +340,7 @@ func IsPublicIP(ip net.IP) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startProxy starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected
|
// startProxy starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected
|
||||||
func (conn *Conn) startProxy(remoteConn net.Conn) error {
|
func (conn *Conn) startProxy(remoteConn net.Conn, remoteWgPort int) error {
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
defer conn.mu.Unlock()
|
defer conn.mu.Unlock()
|
||||||
|
|
||||||
@@ -327,7 +357,7 @@ func (conn *Conn) startProxy(remoteConn net.Conn) error {
|
|||||||
p = proxy.NewWireguardProxy(conn.config.ProxyConfig)
|
p = proxy.NewWireguardProxy(conn.config.ProxyConfig)
|
||||||
peerState.Direct = false
|
peerState.Direct = false
|
||||||
} else {
|
} else {
|
||||||
p = proxy.NewNoProxy(conn.config.ProxyConfig)
|
p = proxy.NewNoProxy(conn.config.ProxyConfig, remoteWgPort)
|
||||||
peerState.Direct = true
|
peerState.Direct = true
|
||||||
}
|
}
|
||||||
conn.proxy = p
|
conn.proxy = p
|
||||||
@@ -386,9 +416,12 @@ func (conn *Conn) cleanup() error {
|
|||||||
peerState := nbStatus.PeerState{PubKey: conn.config.Key}
|
peerState := nbStatus.PeerState{PubKey: conn.config.Key}
|
||||||
peerState.ConnStatus = conn.status.String()
|
peerState.ConnStatus = conn.status.String()
|
||||||
peerState.ConnStatusUpdate = time.Now()
|
peerState.ConnStatusUpdate = time.Now()
|
||||||
|
|
||||||
err := conn.statusRecorder.UpdatePeerState(peerState)
|
err := conn.statusRecorder.UpdatePeerState(peerState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("error while updating peer's %s state, err: %v", conn.config.Key, err)
|
// pretty common error because by that time Engine can already remove the peer and status won't be available.
|
||||||
|
//todo rethink status updates
|
||||||
|
log.Debugf("error while updating peer's %s state, err: %v", conn.config.Key, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("cleaned up connection to peer %s", conn.config.Key)
|
log.Debugf("cleaned up connection to peer %s", conn.config.Key)
|
||||||
@@ -397,12 +430,12 @@ func (conn *Conn) cleanup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetSignalOffer sets a handler function to be triggered by Conn when a new connection offer has to be signalled to the remote peer
|
// SetSignalOffer sets a handler function to be triggered by Conn when a new connection offer has to be signalled to the remote peer
|
||||||
func (conn *Conn) SetSignalOffer(handler func(uFrag string, pwd string) error) {
|
func (conn *Conn) SetSignalOffer(handler func(offer OfferAnswer) error) {
|
||||||
conn.signalOffer = handler
|
conn.signalOffer = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSignalAnswer sets a handler function to be triggered by Conn when a new connection answer has to be signalled to the remote peer
|
// SetSignalAnswer sets a handler function to be triggered by Conn when a new connection answer has to be signalled to the remote peer
|
||||||
func (conn *Conn) SetSignalAnswer(handler func(uFrag string, pwd string) error) {
|
func (conn *Conn) SetSignalAnswer(handler func(answer OfferAnswer) error) {
|
||||||
conn.signalAnswer = handler
|
conn.signalAnswer = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +448,7 @@ func (conn *Conn) SetSignalCandidate(handler func(candidate ice.Candidate) error
|
|||||||
// and then signals them to the remote peer
|
// and then signals them to the remote peer
|
||||||
func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
||||||
if candidate != nil {
|
if candidate != nil {
|
||||||
// log.Debugf("discovered local candidate %s", candidate.String())
|
log.Debugf("discovered local candidate %s", candidate.String())
|
||||||
go func() {
|
go func() {
|
||||||
err := conn.signalCandidate(candidate)
|
err := conn.signalCandidate(candidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -447,8 +480,12 @@ func (conn *Conn) sendAnswer() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("sending asnwer to %s", conn.config.Key)
|
log.Debugf("sending answer to %s", conn.config.Key)
|
||||||
err = conn.signalAnswer(localUFrag, localPwd)
|
err = conn.signalAnswer(OfferAnswer{
|
||||||
|
IceCredentials: IceCredentials{localUFrag, localPwd},
|
||||||
|
WgListenPort: conn.config.LocalWgPort,
|
||||||
|
Version: system.NetbirdVersion(),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -465,7 +502,11 @@ func (conn *Conn) sendOffer() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = conn.signalOffer(localUFrag, localPwd)
|
err = conn.signalOffer(OfferAnswer{
|
||||||
|
IceCredentials: IceCredentials{localUFrag, localPwd},
|
||||||
|
WgListenPort: conn.config.LocalWgPort,
|
||||||
|
Version: system.NetbirdVersion(),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -506,11 +547,11 @@ func (conn *Conn) Status() ConnStatus {
|
|||||||
|
|
||||||
// OnRemoteOffer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
|
// OnRemoteOffer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
|
||||||
// doesn't block, discards the message if connection wasn't ready
|
// doesn't block, discards the message if connection wasn't ready
|
||||||
func (conn *Conn) OnRemoteOffer(remoteAuth IceCredentials) bool {
|
func (conn *Conn) OnRemoteOffer(offer OfferAnswer) bool {
|
||||||
log.Debugf("OnRemoteOffer from peer %s on status %s", conn.config.Key, conn.status.String())
|
log.Debugf("OnRemoteOffer from peer %s on status %s", conn.config.Key, conn.status.String())
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case conn.remoteOffersCh <- remoteAuth:
|
case conn.remoteOffersCh <- offer:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
log.Debugf("OnRemoteOffer skipping message from peer %s on status %s because is not ready", conn.config.Key, conn.status.String())
|
log.Debugf("OnRemoteOffer skipping message from peer %s on status %s because is not ready", conn.config.Key, conn.status.String())
|
||||||
@@ -521,11 +562,11 @@ func (conn *Conn) OnRemoteOffer(remoteAuth IceCredentials) bool {
|
|||||||
|
|
||||||
// OnRemoteAnswer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
|
// OnRemoteAnswer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
|
||||||
// doesn't block, discards the message if connection wasn't ready
|
// doesn't block, discards the message if connection wasn't ready
|
||||||
func (conn *Conn) OnRemoteAnswer(remoteAuth IceCredentials) bool {
|
func (conn *Conn) OnRemoteAnswer(answer OfferAnswer) bool {
|
||||||
log.Debugf("OnRemoteAnswer from peer %s on status %s", conn.config.Key, conn.status.String())
|
log.Debugf("OnRemoteAnswer from peer %s on status %s", conn.config.Key, conn.status.String())
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case conn.remoteAnswerCh <- remoteAuth:
|
case conn.remoteAnswerCh <- answer:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
// connection might not be ready yet to receive so we ignore the message
|
// connection might not be ready yet to receive so we ignore the message
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ var connConf = ConnConfig{
|
|||||||
InterfaceBlackList: nil,
|
InterfaceBlackList: nil,
|
||||||
Timeout: time.Second,
|
Timeout: time.Second,
|
||||||
ProxyConfig: proxy.Config{},
|
ProxyConfig: proxy.Config{},
|
||||||
|
LocalWgPort: 51820,
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewConn_interfaceFilter(t *testing.T) {
|
func TestNewConn_interfaceFilter(t *testing.T) {
|
||||||
@@ -59,9 +60,13 @@ func TestConn_OnRemoteOffer(t *testing.T) {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
accepted := conn.OnRemoteOffer(IceCredentials{
|
accepted := conn.OnRemoteOffer(OfferAnswer{
|
||||||
UFrag: "test",
|
IceCredentials: IceCredentials{
|
||||||
Pwd: "test",
|
UFrag: "test",
|
||||||
|
Pwd: "test",
|
||||||
|
},
|
||||||
|
WgListenPort: 0,
|
||||||
|
Version: "",
|
||||||
})
|
})
|
||||||
if accepted {
|
if accepted {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
@@ -89,9 +94,13 @@ func TestConn_OnRemoteAnswer(t *testing.T) {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
accepted := conn.OnRemoteAnswer(IceCredentials{
|
accepted := conn.OnRemoteAnswer(OfferAnswer{
|
||||||
UFrag: "test",
|
IceCredentials: IceCredentials{
|
||||||
Pwd: "test",
|
UFrag: "test",
|
||||||
|
Pwd: "test",
|
||||||
|
},
|
||||||
|
WgListenPort: 0,
|
||||||
|
Version: "",
|
||||||
})
|
})
|
||||||
if accepted {
|
if accepted {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/netbirdio/netbird/iface"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
@@ -14,10 +13,14 @@ import (
|
|||||||
// In order NoProxy to work, Wireguard port has to be fixed for the time being.
|
// In order NoProxy to work, Wireguard port has to be fixed for the time being.
|
||||||
type NoProxy struct {
|
type NoProxy struct {
|
||||||
config Config
|
config Config
|
||||||
|
// RemoteWgListenPort is a WireGuard port of a remote peer.
|
||||||
|
// It is used instead of the hardcoded 51820 port.
|
||||||
|
RemoteWgListenPort int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNoProxy(config Config) *NoProxy {
|
// NewNoProxy creates a new NoProxy with a provided config and remote peer's WireGuard listen port
|
||||||
return &NoProxy{config: config}
|
func NewNoProxy(config Config, remoteWgPort int) *NoProxy {
|
||||||
|
return &NoProxy{config: config, RemoteWgListenPort: remoteWgPort}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *NoProxy) Close() error {
|
func (p *NoProxy) Close() error {
|
||||||
@@ -36,7 +39,7 @@ func (p *NoProxy) Start(remoteConn net.Conn) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
addr.Port = iface.DefaultWgPort
|
addr.Port = p.RemoteWgListenPort
|
||||||
err = p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
|
err = p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
|
||||||
addr, p.config.PreSharedKey)
|
addr, p.config.PreSharedKey)
|
||||||
|
|
||||||
|
|||||||
285
client/internal/routemanager/client.go
Normal file
285
client/internal/routemanager/client.go
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/client/status"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
type routerPeerStatus struct {
|
||||||
|
connected bool
|
||||||
|
relayed bool
|
||||||
|
direct bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type routesUpdate struct {
|
||||||
|
updateSerial uint64
|
||||||
|
routes []*route.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientNetwork struct {
|
||||||
|
ctx context.Context
|
||||||
|
stop context.CancelFunc
|
||||||
|
statusRecorder *status.Status
|
||||||
|
wgInterface *iface.WGIface
|
||||||
|
routes map[string]*route.Route
|
||||||
|
routeUpdate chan routesUpdate
|
||||||
|
peerStateUpdate chan struct{}
|
||||||
|
routePeersNotifiers map[string]chan struct{}
|
||||||
|
chosenRoute *route.Route
|
||||||
|
network netip.Prefix
|
||||||
|
updateSerial uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClientNetworkWatcher(ctx context.Context, wgInterface *iface.WGIface, statusRecorder *status.Status, network netip.Prefix) *clientNetwork {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
client := &clientNetwork{
|
||||||
|
ctx: ctx,
|
||||||
|
stop: cancel,
|
||||||
|
statusRecorder: statusRecorder,
|
||||||
|
wgInterface: wgInterface,
|
||||||
|
routes: make(map[string]*route.Route),
|
||||||
|
routePeersNotifiers: make(map[string]chan struct{}),
|
||||||
|
routeUpdate: make(chan routesUpdate),
|
||||||
|
peerStateUpdate: make(chan struct{}),
|
||||||
|
network: network,
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClientNetworkID(input *route.Route) string {
|
||||||
|
return input.NetID + "-" + input.Network.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientNetwork) getRouterPeerStatuses() map[string]routerPeerStatus {
|
||||||
|
routePeerStatuses := make(map[string]routerPeerStatus)
|
||||||
|
for _, r := range c.routes {
|
||||||
|
peerStatus, err := c.statusRecorder.GetPeer(r.Peer)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("couldn't fetch peer state: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
routePeerStatuses[r.ID] = routerPeerStatus{
|
||||||
|
connected: peerStatus.ConnStatus == peer.StatusConnected.String(),
|
||||||
|
relayed: peerStatus.Relayed,
|
||||||
|
direct: peerStatus.Direct,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return routePeerStatuses
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientNetwork) getBestRouteFromStatuses(routePeerStatuses map[string]routerPeerStatus) string {
|
||||||
|
var chosen string
|
||||||
|
chosenScore := 0
|
||||||
|
|
||||||
|
currID := ""
|
||||||
|
if c.chosenRoute != nil {
|
||||||
|
currID = c.chosenRoute.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range c.routes {
|
||||||
|
tempScore := 0
|
||||||
|
peerStatus, found := routePeerStatuses[r.ID]
|
||||||
|
if !found || !peerStatus.connected {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r.Metric < route.MaxMetric {
|
||||||
|
metricDiff := route.MaxMetric - r.Metric
|
||||||
|
tempScore = metricDiff * 10
|
||||||
|
}
|
||||||
|
if !peerStatus.relayed {
|
||||||
|
tempScore++
|
||||||
|
}
|
||||||
|
if !peerStatus.direct {
|
||||||
|
tempScore++
|
||||||
|
}
|
||||||
|
if tempScore > chosenScore || (tempScore == chosenScore && currID == r.ID) {
|
||||||
|
chosen = r.ID
|
||||||
|
chosenScore = tempScore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if chosen == "" {
|
||||||
|
var peers []string
|
||||||
|
for _, r := range c.routes {
|
||||||
|
peers = append(peers, r.Peer)
|
||||||
|
}
|
||||||
|
log.Warnf("no route was chosen for network %s because no peers from list %s were connected", c.network, peers)
|
||||||
|
} else if chosen != currID {
|
||||||
|
log.Infof("new chosen route is %s with peer %s with score %d", chosen, c.routes[chosen].Peer, chosenScore)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chosen
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientNetwork) watchPeerStatusChanges(ctx context.Context, peerKey string, peerStateUpdate chan struct{}, closer chan struct{}) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-closer:
|
||||||
|
return
|
||||||
|
case <-c.statusRecorder.GetPeerStateChangeNotifier(peerKey):
|
||||||
|
state, err := c.statusRecorder.GetPeer(peerKey)
|
||||||
|
if err != nil || state.ConnStatus == peer.StatusConnecting.String() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
peerStateUpdate <- struct{}{}
|
||||||
|
log.Debugf("triggered route state update for Peer %s, state: %s", peerKey, state.ConnStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientNetwork) startPeersStatusChangeWatcher() {
|
||||||
|
for _, r := range c.routes {
|
||||||
|
_, found := c.routePeersNotifiers[r.Peer]
|
||||||
|
if !found {
|
||||||
|
c.routePeersNotifiers[r.Peer] = make(chan struct{})
|
||||||
|
go c.watchPeerStatusChanges(c.ctx, r.Peer, c.peerStateUpdate, c.routePeersNotifiers[r.Peer])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientNetwork) removeRouteFromWireguardPeer(peerKey string) error {
|
||||||
|
state, err := c.statusRecorder.GetPeer(peerKey)
|
||||||
|
if err != nil || state.ConnStatus != peer.StatusConnected.String() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.wgInterface.RemoveAllowedIP(peerKey, c.network.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't remove allowed IP %s removed for peer %s, err: %v",
|
||||||
|
c.network, c.chosenRoute.Peer, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientNetwork) removeRouteFromPeerAndSystem() error {
|
||||||
|
if c.chosenRoute != nil {
|
||||||
|
err := c.removeRouteFromWireguardPeer(c.chosenRoute.Peer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = removeFromRouteTableIfNonSystem(c.network, c.wgInterface.GetAddress().IP.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't remove route %s from system, err: %v",
|
||||||
|
c.network, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
routerPeerStatuses := c.getRouterPeerStatuses()
|
||||||
|
|
||||||
|
chosen := c.getBestRouteFromStatuses(routerPeerStatuses)
|
||||||
|
if chosen == "" {
|
||||||
|
err = c.removeRouteFromPeerAndSystem()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.chosenRoute = nil
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.chosenRoute != nil && c.chosenRoute.ID == chosen {
|
||||||
|
if c.chosenRoute.IsEqual(c.routes[chosen]) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.chosenRoute != nil {
|
||||||
|
err = c.removeRouteFromWireguardPeer(c.chosenRoute.Peer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = addToRouteTableIfNoExists(c.network, c.wgInterface.GetAddress().IP.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("route %s couldn't be added for peer %s, err: %v",
|
||||||
|
c.network.String(), c.wgInterface.GetAddress().IP.String(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.chosenRoute = c.routes[chosen]
|
||||||
|
err = c.wgInterface.AddAllowedIP(c.chosenRoute.Peer, c.network.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("couldn't add allowed IP %s added for peer %s, err: %v",
|
||||||
|
c.network, c.chosenRoute.Peer, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientNetwork) sendUpdateToClientNetworkWatcher(update routesUpdate) {
|
||||||
|
go func() {
|
||||||
|
c.routeUpdate <- update
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientNetwork) handleUpdate(update routesUpdate) {
|
||||||
|
updateMap := make(map[string]*route.Route)
|
||||||
|
|
||||||
|
for _, r := range update.routes {
|
||||||
|
updateMap[r.ID] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, r := range c.routes {
|
||||||
|
_, found := updateMap[id]
|
||||||
|
if !found {
|
||||||
|
close(c.routePeersNotifiers[r.Peer])
|
||||||
|
delete(c.routePeersNotifiers, r.Peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.routes = updateMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// peersStateAndUpdateWatcher is the main point of reacting on client network routing events.
|
||||||
|
// All the processing related to the client network should be done here. Thread-safe.
|
||||||
|
func (c *clientNetwork) peersStateAndUpdateWatcher() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.ctx.Done():
|
||||||
|
log.Debugf("stopping watcher for network %s", c.network)
|
||||||
|
err := c.removeRouteFromPeerAndSystem()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case <-c.peerStateUpdate:
|
||||||
|
err := c.recalculateRouteAndUpdatePeerAndSystem()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
case update := <-c.routeUpdate:
|
||||||
|
if update.updateSerial < c.updateSerial {
|
||||||
|
log.Warnf("received a routes update with smaller serial number, ignoring it")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("received a new client network route update for %s", c.network)
|
||||||
|
|
||||||
|
c.handleUpdate(update)
|
||||||
|
|
||||||
|
c.updateSerial = update.updateSerial
|
||||||
|
|
||||||
|
err := c.recalculateRouteAndUpdatePeerAndSystem()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.startPeersStatusChangeWatcher()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
client/internal/routemanager/common_linux_test.go
Normal file
75
client/internal/routemanager/common_linux_test.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
var insertRuleTestCases = []struct {
|
||||||
|
name string
|
||||||
|
inputPair routerPair
|
||||||
|
ipVersion string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Insert Forwarding IPV4 Rule",
|
||||||
|
inputPair: routerPair{
|
||||||
|
ID: "zxa",
|
||||||
|
source: "100.100.100.1/32",
|
||||||
|
destination: "100.100.200.0/24",
|
||||||
|
masquerade: false,
|
||||||
|
},
|
||||||
|
ipVersion: ipv4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Insert Forwarding And Nat IPV4 Rules",
|
||||||
|
inputPair: routerPair{
|
||||||
|
ID: "zxa",
|
||||||
|
source: "100.100.100.1/32",
|
||||||
|
destination: "100.100.200.0/24",
|
||||||
|
masquerade: true,
|
||||||
|
},
|
||||||
|
ipVersion: ipv4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Insert Forwarding IPV6 Rule",
|
||||||
|
inputPair: routerPair{
|
||||||
|
ID: "zxa",
|
||||||
|
source: "fc00::1/128",
|
||||||
|
destination: "fc12::/64",
|
||||||
|
masquerade: false,
|
||||||
|
},
|
||||||
|
ipVersion: ipv6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Insert Forwarding And Nat IPV6 Rules",
|
||||||
|
inputPair: routerPair{
|
||||||
|
ID: "zxa",
|
||||||
|
source: "fc00::1/128",
|
||||||
|
destination: "fc12::/64",
|
||||||
|
masquerade: true,
|
||||||
|
},
|
||||||
|
ipVersion: ipv6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeRuleTestCases = []struct {
|
||||||
|
name string
|
||||||
|
inputPair routerPair
|
||||||
|
ipVersion string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Remove Forwarding And Nat IPV4 Rules",
|
||||||
|
inputPair: routerPair{
|
||||||
|
ID: "zxa",
|
||||||
|
source: "100.100.100.1/32",
|
||||||
|
destination: "100.100.200.0/24",
|
||||||
|
masquerade: true,
|
||||||
|
},
|
||||||
|
ipVersion: ipv4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Remove Forwarding And Nat IPV6 Rules",
|
||||||
|
inputPair: routerPair{
|
||||||
|
ID: "zxa",
|
||||||
|
source: "fc00::1/128",
|
||||||
|
destination: "fc12::/64",
|
||||||
|
masquerade: true,
|
||||||
|
},
|
||||||
|
ipVersion: ipv6,
|
||||||
|
},
|
||||||
|
}
|
||||||
12
client/internal/routemanager/firewall.go
Normal file
12
client/internal/routemanager/firewall.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
type firewallManager interface {
|
||||||
|
// RestoreOrCreateContainers restores or creates a firewall container set of rules, tables and default rules
|
||||||
|
RestoreOrCreateContainers() error
|
||||||
|
// InsertRoutingRules inserts a routing firewall rule
|
||||||
|
InsertRoutingRules(pair routerPair) error
|
||||||
|
// RemoveRoutingRules removes a routing firewall rule
|
||||||
|
RemoveRoutingRules(pair routerPair) error
|
||||||
|
// CleanRoutingRules cleans a firewall set of containers
|
||||||
|
CleanRoutingRules()
|
||||||
|
}
|
||||||
67
client/internal/routemanager/firewall_linux.go
Normal file
67
client/internal/routemanager/firewall_linux.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/coreos/go-iptables/iptables"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
import "github.com/google/nftables"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ipv6Forwarding = "netbird-rt-ipv6-forwarding"
|
||||||
|
ipv4Forwarding = "netbird-rt-ipv4-forwarding"
|
||||||
|
ipv6Nat = "netbird-rt-ipv6-nat"
|
||||||
|
ipv4Nat = "netbird-rt-ipv4-nat"
|
||||||
|
natFormat = "netbird-nat-%s"
|
||||||
|
forwardingFormat = "netbird-fwd-%s"
|
||||||
|
inNatFormat = "netbird-nat-in-%s"
|
||||||
|
inForwardingFormat = "netbird-fwd-in-%s"
|
||||||
|
ipv6 = "ipv6"
|
||||||
|
ipv4 = "ipv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func genKey(format string, input string) string {
|
||||||
|
return fmt.Sprintf(format, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFirewall if supported, returns an iptables manager, otherwise returns a nftables manager
|
||||||
|
func NewFirewall(parentCTX context.Context) firewallManager {
|
||||||
|
ctx, cancel := context.WithCancel(parentCTX)
|
||||||
|
|
||||||
|
if isIptablesSupported() {
|
||||||
|
log.Debugf("iptables is supported")
|
||||||
|
ipv4Client, _ := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
|
ipv6Client, _ := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||||
|
|
||||||
|
return &iptablesManager{
|
||||||
|
ctx: ctx,
|
||||||
|
stop: cancel,
|
||||||
|
ipv4Client: ipv4Client,
|
||||||
|
ipv6Client: ipv6Client,
|
||||||
|
rules: make(map[string]map[string][]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("iptables is not supported, using nftables")
|
||||||
|
|
||||||
|
manager := &nftablesManager{
|
||||||
|
ctx: ctx,
|
||||||
|
stop: cancel,
|
||||||
|
conn: &nftables.Conn{},
|
||||||
|
chains: make(map[string]map[string]*nftables.Chain),
|
||||||
|
rules: make(map[string]*nftables.Rule),
|
||||||
|
}
|
||||||
|
|
||||||
|
return manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInPair(pair routerPair) routerPair {
|
||||||
|
return routerPair{
|
||||||
|
ID: pair.ID,
|
||||||
|
// invert source/destination
|
||||||
|
source: pair.destination,
|
||||||
|
destination: pair.source,
|
||||||
|
masquerade: pair.masquerade,
|
||||||
|
}
|
||||||
|
}
|
||||||
27
client/internal/routemanager/firewall_nonlinux.go
Normal file
27
client/internal/routemanager/firewall_nonlinux.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package routemanager
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type unimplementedFirewall struct{}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFirewall returns an unimplemented Firewall manager
|
||||||
|
func NewFirewall(parentCtx context.Context) firewallManager {
|
||||||
|
return unimplementedFirewall{}
|
||||||
|
}
|
||||||
436
client/internal/routemanager/iptables_linux.go
Normal file
436
client/internal/routemanager/iptables_linux.go
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/coreos/go-iptables/iptables"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/netip"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isIptablesSupported() bool {
|
||||||
|
_, err4 := exec.LookPath("iptables")
|
||||||
|
_, err6 := exec.LookPath("ip6tables")
|
||||||
|
return err4 == nil && err6 == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// constants needed to manage and create iptable rules
|
||||||
|
const (
|
||||||
|
iptablesFilterTable = "filter"
|
||||||
|
iptablesNatTable = "nat"
|
||||||
|
iptablesForwardChain = "FORWARD"
|
||||||
|
iptablesPostRoutingChain = "POSTROUTING"
|
||||||
|
iptablesRoutingNatChain = "NETBIRD-RT-NAT"
|
||||||
|
iptablesRoutingForwardingChain = "NETBIRD-RT-FWD"
|
||||||
|
routingFinalForwardJump = "ACCEPT"
|
||||||
|
routingFinalNatJump = "MASQUERADE"
|
||||||
|
)
|
||||||
|
|
||||||
|
// some presets for building nftable rules
|
||||||
|
var (
|
||||||
|
iptablesDefaultForwardingRule = []string{"-j", iptablesRoutingForwardingChain, "-m", "comment", "--comment"}
|
||||||
|
iptablesDefaultNetbirdForwardingRule = []string{"-j", "RETURN"}
|
||||||
|
iptablesDefaultNatRule = []string{"-j", iptablesRoutingNatChain, "-m", "comment", "--comment"}
|
||||||
|
iptablesDefaultNetbirdNatRule = []string{"-j", "RETURN"}
|
||||||
|
)
|
||||||
|
|
||||||
|
type iptablesManager struct {
|
||||||
|
ctx context.Context
|
||||||
|
stop context.CancelFunc
|
||||||
|
ipv4Client *iptables.IPTables
|
||||||
|
ipv6Client *iptables.IPTables
|
||||||
|
rules map[string]map[string][]string
|
||||||
|
mux sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanRoutingRules cleans existing iptables resources that we created by the agent
|
||||||
|
func (i *iptablesManager) CleanRoutingRules() {
|
||||||
|
i.mux.Lock()
|
||||||
|
defer i.mux.Unlock()
|
||||||
|
|
||||||
|
err := i.cleanJumpRules()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("flushing tables")
|
||||||
|
errMSGFormat := "iptables: failed cleaning %s chain %s,error: %v"
|
||||||
|
err = i.ipv4Client.ClearAndDeleteChain(iptablesFilterTable, iptablesRoutingForwardingChain)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(errMSGFormat, ipv4, iptablesRoutingForwardingChain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.ipv4Client.ClearAndDeleteChain(iptablesNatTable, iptablesRoutingNatChain)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(errMSGFormat, ipv4, iptablesRoutingNatChain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.ipv6Client.ClearAndDeleteChain(iptablesFilterTable, iptablesRoutingForwardingChain)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(errMSGFormat, ipv6, iptablesRoutingForwardingChain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.ipv6Client.ClearAndDeleteChain(iptablesNatTable, iptablesRoutingNatChain)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(errMSGFormat, ipv6, iptablesRoutingNatChain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("done cleaning up iptables rules")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreOrCreateContainers restores existing iptables containers (chains and rules)
|
||||||
|
// if they don't exist, we create them
|
||||||
|
func (i *iptablesManager) RestoreOrCreateContainers() error {
|
||||||
|
i.mux.Lock()
|
||||||
|
defer i.mux.Unlock()
|
||||||
|
|
||||||
|
if i.rules[ipv4][ipv4Forwarding] != nil && i.rules[ipv6][ipv6Forwarding] != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errMSGFormat := "iptables: failed creating %s chain %s,error: %v"
|
||||||
|
|
||||||
|
err := createChain(i.ipv4Client, iptablesFilterTable, iptablesRoutingForwardingChain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(errMSGFormat, ipv4, iptablesRoutingForwardingChain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createChain(i.ipv4Client, iptablesNatTable, iptablesRoutingNatChain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(errMSGFormat, ipv4, iptablesRoutingNatChain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createChain(i.ipv6Client, iptablesFilterTable, iptablesRoutingForwardingChain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(errMSGFormat, ipv6, iptablesRoutingForwardingChain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createChain(i.ipv6Client, iptablesNatTable, iptablesRoutingNatChain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(errMSGFormat, ipv6, iptablesRoutingNatChain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.restoreRules(i.ipv4Client)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("iptables: error while restoring ipv4 rules: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.restoreRules(i.ipv6Client)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("iptables: error while restoring ipv6 rules: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.addJumpRules()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("iptables: error while creating jump rules: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addJumpRules create jump rules to send packets to NetBird chains
|
||||||
|
func (i *iptablesManager) addJumpRules() error {
|
||||||
|
err := i.cleanJumpRules()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rule := append(iptablesDefaultForwardingRule, ipv4Forwarding)
|
||||||
|
err = i.ipv4Client.Insert(iptablesFilterTable, iptablesForwardChain, 1, rule...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
i.rules[ipv4][ipv4Forwarding] = rule
|
||||||
|
|
||||||
|
rule = append(iptablesDefaultNatRule, ipv4Nat)
|
||||||
|
err = i.ipv4Client.Insert(iptablesNatTable, iptablesPostRoutingChain, 1, rule...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.rules[ipv4][ipv4Nat] = rule
|
||||||
|
|
||||||
|
rule = append(iptablesDefaultForwardingRule, ipv6Forwarding)
|
||||||
|
err = i.ipv6Client.Insert(iptablesFilterTable, iptablesForwardChain, 1, rule...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.rules[ipv6][ipv6Forwarding] = rule
|
||||||
|
|
||||||
|
rule = append(iptablesDefaultNatRule, ipv6Nat)
|
||||||
|
err = i.ipv6Client.Insert(iptablesNatTable, iptablesPostRoutingChain, 1, rule...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.rules[ipv6][ipv6Nat] = rule
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanJumpRules cleans jump rules that was sending packets to NetBird chains
|
||||||
|
func (i *iptablesManager) cleanJumpRules() error {
|
||||||
|
var err error
|
||||||
|
errMSGFormat := "iptables: failed cleaning rule from %s chain %s,err: %v"
|
||||||
|
rule, found := i.rules[ipv4][ipv4Forwarding]
|
||||||
|
if found {
|
||||||
|
log.Debugf("iptables: removing %s rule: %s ", ipv4, ipv4Forwarding)
|
||||||
|
err = i.ipv4Client.DeleteIfExists(iptablesFilterTable, iptablesForwardChain, rule...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(errMSGFormat, ipv4, iptablesForwardChain, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rule, found = i.rules[ipv4][ipv4Nat]
|
||||||
|
if found {
|
||||||
|
log.Debugf("iptables: removing %s rule: %s ", ipv4, ipv4Nat)
|
||||||
|
err = i.ipv4Client.DeleteIfExists(iptablesNatTable, iptablesPostRoutingChain, rule...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(errMSGFormat, ipv4, iptablesPostRoutingChain, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rule, found = i.rules[ipv6][ipv6Forwarding]
|
||||||
|
if found {
|
||||||
|
log.Debugf("iptables: removing %s rule: %s ", ipv6, ipv6Forwarding)
|
||||||
|
err = i.ipv6Client.DeleteIfExists(iptablesFilterTable, iptablesForwardChain, rule...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(errMSGFormat, ipv6, iptablesForwardChain, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rule, found = i.rules[ipv6][ipv6Nat]
|
||||||
|
if found {
|
||||||
|
log.Debugf("iptables: removing %s rule: %s ", ipv6, ipv6Nat)
|
||||||
|
err = i.ipv6Client.DeleteIfExists(iptablesNatTable, iptablesPostRoutingChain, rule...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(errMSGFormat, ipv6, iptablesPostRoutingChain, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func iptablesProtoToString(proto iptables.Protocol) string {
|
||||||
|
if proto == iptables.ProtocolIPv6 {
|
||||||
|
return ipv6
|
||||||
|
}
|
||||||
|
return ipv4
|
||||||
|
}
|
||||||
|
|
||||||
|
// restoreRules restores existing NetBird rules
|
||||||
|
func (i *iptablesManager) restoreRules(iptablesClient *iptables.IPTables) error {
|
||||||
|
ipVersion := iptablesProtoToString(iptablesClient.Proto())
|
||||||
|
|
||||||
|
if i.rules[ipVersion] == nil {
|
||||||
|
i.rules[ipVersion] = make(map[string][]string)
|
||||||
|
}
|
||||||
|
table := iptablesFilterTable
|
||||||
|
for _, chain := range []string{iptablesForwardChain, iptablesRoutingForwardingChain} {
|
||||||
|
rules, err := iptablesClient.List(table, chain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, ruleString := range rules {
|
||||||
|
rule := strings.Fields(ruleString)
|
||||||
|
id := getRuleRouteID(rule)
|
||||||
|
if id != "" {
|
||||||
|
i.rules[ipVersion][id] = rule[2:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table = iptablesNatTable
|
||||||
|
for _, chain := range []string{iptablesPostRoutingChain, iptablesRoutingNatChain} {
|
||||||
|
rules, err := iptablesClient.List(table, chain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, ruleString := range rules {
|
||||||
|
rule := strings.Fields(ruleString)
|
||||||
|
id := getRuleRouteID(rule)
|
||||||
|
if id != "" {
|
||||||
|
i.rules[ipVersion][id] = rule[2:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createChain create NetBird chains
|
||||||
|
func createChain(iptables *iptables.IPTables, table, newChain string) error {
|
||||||
|
chains, err := iptables.ListChains(table)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't get %s %s table chains, error: %v", iptablesProtoToString(iptables.Proto()), table, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldCreateChain := true
|
||||||
|
for _, chain := range chains {
|
||||||
|
if chain == newChain {
|
||||||
|
shouldCreateChain = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldCreateChain {
|
||||||
|
err = iptables.NewChain(table, newChain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't create %s chain %s in %s table, error: %v", iptablesProtoToString(iptables.Proto()), newChain, table, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if table == iptablesNatTable {
|
||||||
|
err = iptables.Append(table, newChain, iptablesDefaultNetbirdNatRule...)
|
||||||
|
} else {
|
||||||
|
err = iptables.Append(table, newChain, iptablesDefaultNetbirdForwardingRule...)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't create %s chain %s default rule, error: %v", iptablesProtoToString(iptables.Proto()), newChain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// genRuleSpec generates rule specification with comment identifier
|
||||||
|
func genRuleSpec(jump, id, source, destination string) []string {
|
||||||
|
return []string{"-s", source, "-d", destination, "-j", jump, "-m", "comment", "--comment", id}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRuleRouteID returns the rule ID if matches our prefix
|
||||||
|
func getRuleRouteID(rule []string) string {
|
||||||
|
for i, flag := range rule {
|
||||||
|
if flag == "--comment" {
|
||||||
|
id := rule[i+1]
|
||||||
|
if strings.HasPrefix(id, "netbird-") {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertRoutingRules inserts an iptables rule pair to the forwarding chain and if enabled, to the nat chain
|
||||||
|
func (i *iptablesManager) InsertRoutingRules(pair routerPair) error {
|
||||||
|
i.mux.Lock()
|
||||||
|
defer i.mux.Unlock()
|
||||||
|
|
||||||
|
err := i.insertRoutingRule(forwardingFormat, iptablesFilterTable, iptablesRoutingForwardingChain, routingFinalForwardJump, pair)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.insertRoutingRule(inForwardingFormat, iptablesFilterTable, iptablesRoutingForwardingChain, routingFinalForwardJump, getInPair(pair))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pair.masquerade {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.insertRoutingRule(natFormat, iptablesNatTable, iptablesRoutingNatChain, routingFinalNatJump, pair)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.insertRoutingRule(inNatFormat, iptablesNatTable, iptablesRoutingNatChain, routingFinalNatJump, getInPair(pair))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertRoutingRule inserts an iptable rule
|
||||||
|
func (i *iptablesManager) insertRoutingRule(keyFormat, table, chain, jump string, pair routerPair) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
prefix := netip.MustParsePrefix(pair.source)
|
||||||
|
ipVersion := ipv4
|
||||||
|
iptablesClient := i.ipv4Client
|
||||||
|
if prefix.Addr().Unmap().Is6() {
|
||||||
|
iptablesClient = i.ipv6Client
|
||||||
|
ipVersion = ipv6
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleKey := genKey(keyFormat, pair.ID)
|
||||||
|
rule := genRuleSpec(jump, ruleKey, pair.source, pair.destination)
|
||||||
|
existingRule, found := i.rules[ipVersion][ruleKey]
|
||||||
|
if found {
|
||||||
|
err = iptablesClient.DeleteIfExists(table, chain, existingRule...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("iptables: error while removing existing %s rule for %s: %v", getIptablesRuleType(table), pair.destination, err)
|
||||||
|
}
|
||||||
|
delete(i.rules[ipVersion], ruleKey)
|
||||||
|
}
|
||||||
|
err = iptablesClient.Insert(table, chain, 1, rule...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("iptables: error while adding new %s rule for %s: %v", getIptablesRuleType(table), pair.destination, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
i.rules[ipVersion][ruleKey] = rule
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRoutingRules removes an iptables rule pair from forwarding and nat chains
|
||||||
|
func (i *iptablesManager) RemoveRoutingRules(pair routerPair) error {
|
||||||
|
i.mux.Lock()
|
||||||
|
defer i.mux.Unlock()
|
||||||
|
|
||||||
|
err := i.removeRoutingRule(forwardingFormat, iptablesFilterTable, iptablesRoutingForwardingChain, pair)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.removeRoutingRule(inForwardingFormat, iptablesFilterTable, iptablesRoutingForwardingChain, getInPair(pair))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pair.masquerade {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.removeRoutingRule(natFormat, iptablesNatTable, iptablesRoutingNatChain, pair)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.removeRoutingRule(inNatFormat, iptablesNatTable, iptablesRoutingNatChain, getInPair(pair))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeRoutingRule removes an iptables rule
|
||||||
|
func (i *iptablesManager) removeRoutingRule(keyFormat, table, chain string, pair routerPair) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
prefix := netip.MustParsePrefix(pair.source)
|
||||||
|
ipVersion := ipv4
|
||||||
|
iptablesClient := i.ipv4Client
|
||||||
|
if prefix.Addr().Unmap().Is6() {
|
||||||
|
iptablesClient = i.ipv6Client
|
||||||
|
ipVersion = ipv6
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleKey := genKey(keyFormat, pair.ID)
|
||||||
|
existingRule, found := i.rules[ipVersion][ruleKey]
|
||||||
|
if found {
|
||||||
|
err = iptablesClient.DeleteIfExists(table, chain, existingRule...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("iptables: error while removing existing %s rule for %s: %v", getIptablesRuleType(table), pair.destination, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(i.rules[ipVersion], ruleKey)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIptablesRuleType(table string) string {
|
||||||
|
ruleType := "forwarding"
|
||||||
|
if table == iptablesNatTable {
|
||||||
|
ruleType = "nat"
|
||||||
|
}
|
||||||
|
return ruleType
|
||||||
|
}
|
||||||
300
client/internal/routemanager/iptables_linux_test.go
Normal file
300
client/internal/routemanager/iptables_linux_test.go
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/coreos/go-iptables/iptables"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIptablesManager_RestoreOrCreateContainers(t *testing.T) {
|
||||||
|
|
||||||
|
if !isIptablesSupported() {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
|
ipv4Client, _ := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
|
ipv6Client, _ := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||||
|
|
||||||
|
manager := &iptablesManager{
|
||||||
|
ctx: ctx,
|
||||||
|
stop: cancel,
|
||||||
|
ipv4Client: ipv4Client,
|
||||||
|
ipv6Client: ipv6Client,
|
||||||
|
rules: make(map[string]map[string][]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
defer manager.CleanRoutingRules()
|
||||||
|
|
||||||
|
err := manager.RestoreOrCreateContainers()
|
||||||
|
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[ipv4], 2, "should have created minimal rules for ipv4")
|
||||||
|
|
||||||
|
exists, err := ipv4Client.Exists(iptablesFilterTable, iptablesForwardChain, manager.rules[ipv4][ipv4Forwarding]...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", ipv4, iptablesFilterTable, iptablesForwardChain)
|
||||||
|
require.True(t, exists, "forwarding rule should exist")
|
||||||
|
|
||||||
|
exists, err = ipv4Client.Exists(iptablesNatTable, iptablesPostRoutingChain, manager.rules[ipv4][ipv4Nat]...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", ipv4, iptablesNatTable, iptablesPostRoutingChain)
|
||||||
|
require.True(t, exists, "postrouting rule should exist")
|
||||||
|
|
||||||
|
require.Len(t, manager.rules[ipv6], 2, "should have created minimal rules for ipv6")
|
||||||
|
|
||||||
|
exists, err = ipv6Client.Exists(iptablesFilterTable, iptablesForwardChain, manager.rules[ipv6][ipv6Forwarding]...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", ipv6, iptablesFilterTable, iptablesForwardChain)
|
||||||
|
require.True(t, exists, "forwarding rule should exist")
|
||||||
|
|
||||||
|
exists, err = ipv6Client.Exists(iptablesNatTable, iptablesPostRoutingChain, manager.rules[ipv6][ipv6Nat]...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", ipv6, iptablesNatTable, iptablesPostRoutingChain)
|
||||||
|
require.True(t, exists, "postrouting rule should exist")
|
||||||
|
|
||||||
|
pair := routerPair{
|
||||||
|
ID: "abc",
|
||||||
|
source: "100.100.100.1/32",
|
||||||
|
destination: "100.100.100.0/24",
|
||||||
|
masquerade: true,
|
||||||
|
}
|
||||||
|
forward4RuleKey := genKey(forwardingFormat, pair.ID)
|
||||||
|
forward4Rule := genRuleSpec(routingFinalForwardJump, forward4RuleKey, pair.source, pair.destination)
|
||||||
|
|
||||||
|
err = ipv4Client.Insert(iptablesFilterTable, iptablesRoutingForwardingChain, 1, forward4Rule...)
|
||||||
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
|
nat4RuleKey := genKey(natFormat, pair.ID)
|
||||||
|
nat4Rule := genRuleSpec(routingFinalNatJump, nat4RuleKey, pair.source, pair.destination)
|
||||||
|
|
||||||
|
err = ipv4Client.Insert(iptablesNatTable, iptablesRoutingNatChain, 1, nat4Rule...)
|
||||||
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
|
pair = routerPair{
|
||||||
|
ID: "abc",
|
||||||
|
source: "fc00::1/128",
|
||||||
|
destination: "fc11::/64",
|
||||||
|
masquerade: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
forward6RuleKey := genKey(forwardingFormat, pair.ID)
|
||||||
|
forward6Rule := genRuleSpec(routingFinalForwardJump, forward6RuleKey, pair.source, pair.destination)
|
||||||
|
|
||||||
|
err = ipv6Client.Insert(iptablesFilterTable, iptablesRoutingForwardingChain, 1, forward6Rule...)
|
||||||
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
|
nat6RuleKey := genKey(natFormat, pair.ID)
|
||||||
|
nat6Rule := genRuleSpec(routingFinalNatJump, nat6RuleKey, pair.source, pair.destination)
|
||||||
|
|
||||||
|
err = ipv6Client.Insert(iptablesNatTable, iptablesRoutingNatChain, 1, nat6Rule...)
|
||||||
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
|
delete(manager.rules, ipv4)
|
||||||
|
delete(manager.rules, ipv6)
|
||||||
|
|
||||||
|
err = manager.RestoreOrCreateContainers()
|
||||||
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
require.Len(t, manager.rules[ipv4], 4, "should have restored all rules for ipv4")
|
||||||
|
|
||||||
|
foundRule, found := manager.rules[ipv4][forward4RuleKey]
|
||||||
|
require.True(t, found, "forwarding rule should exist in the map")
|
||||||
|
require.Equal(t, forward4Rule[:4], foundRule[:4], "stored forwarding rule should match")
|
||||||
|
|
||||||
|
foundRule, found = manager.rules[ipv4][nat4RuleKey]
|
||||||
|
require.True(t, found, "nat rule should exist in the map")
|
||||||
|
require.Equal(t, nat4Rule[:4], foundRule[:4], "stored nat rule should match")
|
||||||
|
|
||||||
|
require.Len(t, manager.rules[ipv6], 4, "should have restored all rules for ipv6")
|
||||||
|
|
||||||
|
foundRule, found = manager.rules[ipv6][forward6RuleKey]
|
||||||
|
require.True(t, found, "forwarding rule should exist in the map")
|
||||||
|
require.Equal(t, forward6Rule[:4], foundRule[:4], "stored forward rule should match")
|
||||||
|
|
||||||
|
foundRule, found = manager.rules[ipv6][nat6RuleKey]
|
||||||
|
require.True(t, found, "nat rule should exist in the map")
|
||||||
|
require.Equal(t, nat6Rule[:4], foundRule[:4], "stored nat rule should match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIptablesManager_InsertRoutingRules(t *testing.T) {
|
||||||
|
|
||||||
|
if !isIptablesSupported() {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range insertRuleTestCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
|
ipv4Client, _ := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
|
ipv6Client, _ := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||||
|
iptablesClient := ipv4Client
|
||||||
|
if testCase.ipVersion == ipv6 {
|
||||||
|
iptablesClient = ipv6Client
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := &iptablesManager{
|
||||||
|
ctx: ctx,
|
||||||
|
stop: cancel,
|
||||||
|
ipv4Client: ipv4Client,
|
||||||
|
ipv6Client: ipv6Client,
|
||||||
|
rules: make(map[string]map[string][]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
defer manager.CleanRoutingRules()
|
||||||
|
|
||||||
|
err := manager.RestoreOrCreateContainers()
|
||||||
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
err = manager.InsertRoutingRules(testCase.inputPair)
|
||||||
|
require.NoError(t, err, "forwarding pair should be inserted")
|
||||||
|
|
||||||
|
forwardRuleKey := genKey(forwardingFormat, testCase.inputPair.ID)
|
||||||
|
forwardRule := genRuleSpec(routingFinalForwardJump, forwardRuleKey, testCase.inputPair.source, testCase.inputPair.destination)
|
||||||
|
|
||||||
|
exists, err := iptablesClient.Exists(iptablesFilterTable, iptablesRoutingForwardingChain, forwardRule...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesFilterTable, iptablesRoutingForwardingChain)
|
||||||
|
require.True(t, exists, "forwarding rule should exist")
|
||||||
|
|
||||||
|
foundRule, found := manager.rules[testCase.ipVersion][forwardRuleKey]
|
||||||
|
require.True(t, found, "forwarding rule should exist in the manager map")
|
||||||
|
require.Equal(t, forwardRule[:4], foundRule[:4], "stored forwarding rule should match")
|
||||||
|
|
||||||
|
inForwardRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
|
||||||
|
inForwardRule := genRuleSpec(routingFinalForwardJump, inForwardRuleKey, getInPair(testCase.inputPair).source, getInPair(testCase.inputPair).destination)
|
||||||
|
|
||||||
|
exists, err = iptablesClient.Exists(iptablesFilterTable, iptablesRoutingForwardingChain, inForwardRule...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesFilterTable, iptablesRoutingForwardingChain)
|
||||||
|
require.True(t, exists, "income forwarding rule should exist")
|
||||||
|
|
||||||
|
foundRule, found = manager.rules[testCase.ipVersion][inForwardRuleKey]
|
||||||
|
require.True(t, found, "income forwarding rule should exist in the manager map")
|
||||||
|
require.Equal(t, inForwardRule[:4], foundRule[:4], "stored income forwarding rule should match")
|
||||||
|
|
||||||
|
natRuleKey := genKey(natFormat, testCase.inputPair.ID)
|
||||||
|
natRule := genRuleSpec(routingFinalNatJump, natRuleKey, testCase.inputPair.source, testCase.inputPair.destination)
|
||||||
|
|
||||||
|
exists, err = iptablesClient.Exists(iptablesNatTable, iptablesRoutingNatChain, natRule...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesNatTable, iptablesRoutingNatChain)
|
||||||
|
if testCase.inputPair.masquerade {
|
||||||
|
require.True(t, exists, "nat rule should be created")
|
||||||
|
foundNatRule, foundNat := manager.rules[testCase.ipVersion][natRuleKey]
|
||||||
|
require.True(t, foundNat, "nat rule should exist in the map")
|
||||||
|
require.Equal(t, natRule[:4], foundNatRule[:4], "stored nat rule should match")
|
||||||
|
} else {
|
||||||
|
require.False(t, exists, "nat rule should not be created")
|
||||||
|
_, foundNat := manager.rules[testCase.ipVersion][natRuleKey]
|
||||||
|
require.False(t, foundNat, "nat rule should not exist in the map")
|
||||||
|
}
|
||||||
|
|
||||||
|
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
|
||||||
|
inNatRule := genRuleSpec(routingFinalNatJump, inNatRuleKey, getInPair(testCase.inputPair).source, getInPair(testCase.inputPair).destination)
|
||||||
|
|
||||||
|
exists, err = iptablesClient.Exists(iptablesNatTable, iptablesRoutingNatChain, inNatRule...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesNatTable, iptablesRoutingNatChain)
|
||||||
|
if testCase.inputPair.masquerade {
|
||||||
|
require.True(t, exists, "income nat rule should be created")
|
||||||
|
foundNatRule, foundNat := manager.rules[testCase.ipVersion][inNatRuleKey]
|
||||||
|
require.True(t, foundNat, "income nat rule should exist in the map")
|
||||||
|
require.Equal(t, inNatRule[:4], foundNatRule[:4], "stored income nat rule should match")
|
||||||
|
} else {
|
||||||
|
require.False(t, exists, "nat rule should not be created")
|
||||||
|
_, foundNat := manager.rules[testCase.ipVersion][inNatRuleKey]
|
||||||
|
require.False(t, foundNat, "income nat rule should not exist in the map")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIptablesManager_RemoveRoutingRules(t *testing.T) {
|
||||||
|
|
||||||
|
if !isIptablesSupported() {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range removeRuleTestCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
|
ipv4Client, _ := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
|
ipv6Client, _ := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||||
|
iptablesClient := ipv4Client
|
||||||
|
if testCase.ipVersion == ipv6 {
|
||||||
|
iptablesClient = ipv6Client
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := &iptablesManager{
|
||||||
|
ctx: ctx,
|
||||||
|
stop: cancel,
|
||||||
|
ipv4Client: ipv4Client,
|
||||||
|
ipv6Client: ipv6Client,
|
||||||
|
rules: make(map[string]map[string][]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
defer manager.CleanRoutingRules()
|
||||||
|
|
||||||
|
err := manager.RestoreOrCreateContainers()
|
||||||
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
forwardRuleKey := genKey(forwardingFormat, testCase.inputPair.ID)
|
||||||
|
forwardRule := genRuleSpec(routingFinalForwardJump, forwardRuleKey, testCase.inputPair.source, testCase.inputPair.destination)
|
||||||
|
|
||||||
|
err = iptablesClient.Insert(iptablesFilterTable, iptablesRoutingForwardingChain, 1, forwardRule...)
|
||||||
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
|
inForwardRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
|
||||||
|
inForwardRule := genRuleSpec(routingFinalForwardJump, inForwardRuleKey, getInPair(testCase.inputPair).source, getInPair(testCase.inputPair).destination)
|
||||||
|
|
||||||
|
err = iptablesClient.Insert(iptablesFilterTable, iptablesRoutingForwardingChain, 1, inForwardRule...)
|
||||||
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
|
natRuleKey := genKey(natFormat, testCase.inputPair.ID)
|
||||||
|
natRule := genRuleSpec(routingFinalNatJump, natRuleKey, testCase.inputPair.source, testCase.inputPair.destination)
|
||||||
|
|
||||||
|
err = iptablesClient.Insert(iptablesNatTable, iptablesRoutingNatChain, 1, natRule...)
|
||||||
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
|
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
|
||||||
|
inNatRule := genRuleSpec(routingFinalNatJump, inNatRuleKey, getInPair(testCase.inputPair).source, getInPair(testCase.inputPair).destination)
|
||||||
|
|
||||||
|
err = iptablesClient.Insert(iptablesNatTable, iptablesRoutingNatChain, 1, inNatRule...)
|
||||||
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
|
delete(manager.rules, ipv4)
|
||||||
|
delete(manager.rules, ipv6)
|
||||||
|
|
||||||
|
err = manager.RestoreOrCreateContainers()
|
||||||
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
err = manager.RemoveRoutingRules(testCase.inputPair)
|
||||||
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
exists, err := iptablesClient.Exists(iptablesFilterTable, iptablesRoutingForwardingChain, forwardRule...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesFilterTable, iptablesRoutingForwardingChain)
|
||||||
|
require.False(t, exists, "forwarding rule should not exist")
|
||||||
|
|
||||||
|
_, found := manager.rules[testCase.ipVersion][forwardRuleKey]
|
||||||
|
require.False(t, found, "forwarding rule should exist in the manager map")
|
||||||
|
|
||||||
|
exists, err = iptablesClient.Exists(iptablesFilterTable, iptablesRoutingForwardingChain, inForwardRule...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesFilterTable, iptablesRoutingForwardingChain)
|
||||||
|
require.False(t, exists, "income forwarding rule should not exist")
|
||||||
|
|
||||||
|
_, found = manager.rules[testCase.ipVersion][inForwardRuleKey]
|
||||||
|
require.False(t, found, "income forwarding rule should exist in the manager map")
|
||||||
|
|
||||||
|
exists, err = iptablesClient.Exists(iptablesNatTable, iptablesRoutingNatChain, natRule...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesNatTable, iptablesRoutingNatChain)
|
||||||
|
require.False(t, exists, "nat rule should not exist")
|
||||||
|
|
||||||
|
_, found = manager.rules[testCase.ipVersion][natRuleKey]
|
||||||
|
require.False(t, found, "nat rule should exist in the manager map")
|
||||||
|
|
||||||
|
exists, err = iptablesClient.Exists(iptablesNatTable, iptablesRoutingNatChain, inNatRule...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesNatTable, iptablesRoutingNatChain)
|
||||||
|
require.False(t, exists, "income nat rule should not exist")
|
||||||
|
|
||||||
|
_, found = manager.rules[testCase.ipVersion][inNatRuleKey]
|
||||||
|
require.False(t, found, "income nat rule should exist in the manager map")
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
181
client/internal/routemanager/manager.go
Normal file
181
client/internal/routemanager/manager.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/status"
|
||||||
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager is a route manager interface
|
||||||
|
type Manager interface {
|
||||||
|
UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) error
|
||||||
|
Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultManager is the default instance of a route manager
|
||||||
|
type DefaultManager struct {
|
||||||
|
ctx context.Context
|
||||||
|
stop context.CancelFunc
|
||||||
|
mux sync.Mutex
|
||||||
|
clientNetworks map[string]*clientNetwork
|
||||||
|
serverRoutes map[string]*route.Route
|
||||||
|
serverRouter *serverRouter
|
||||||
|
statusRecorder *status.Status
|
||||||
|
wgInterface *iface.WGIface
|
||||||
|
pubKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager returns a new route manager
|
||||||
|
func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface, statusRecorder *status.Status) *DefaultManager {
|
||||||
|
mCTX, cancel := context.WithCancel(ctx)
|
||||||
|
return &DefaultManager{
|
||||||
|
ctx: mCTX,
|
||||||
|
stop: cancel,
|
||||||
|
clientNetworks: make(map[string]*clientNetwork),
|
||||||
|
serverRoutes: make(map[string]*route.Route),
|
||||||
|
serverRouter: &serverRouter{
|
||||||
|
routes: make(map[string]*route.Route),
|
||||||
|
netForwardHistoryEnabled: isNetForwardHistoryEnabled(),
|
||||||
|
firewall: NewFirewall(ctx),
|
||||||
|
},
|
||||||
|
statusRecorder: statusRecorder,
|
||||||
|
wgInterface: wgInterface,
|
||||||
|
pubKey: pubKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the manager watchers and clean firewall rules
|
||||||
|
func (m *DefaultManager) Stop() {
|
||||||
|
m.stop()
|
||||||
|
m.serverRouter.firewall.CleanRoutingRules()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DefaultManager) updateClientNetworks(updateSerial uint64, networks map[string][]*route.Route) {
|
||||||
|
// removing routes that do not exist as per the update from the Management service.
|
||||||
|
for id, client := range m.clientNetworks {
|
||||||
|
_, found := networks[id]
|
||||||
|
if !found {
|
||||||
|
log.Debugf("stopping client network watcher, %s", id)
|
||||||
|
client.stop()
|
||||||
|
delete(m.clientNetworks, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, routes := range networks {
|
||||||
|
clientNetworkWatcher, found := m.clientNetworks[id]
|
||||||
|
if !found {
|
||||||
|
clientNetworkWatcher = newClientNetworkWatcher(m.ctx, m.wgInterface, m.statusRecorder, routes[0].Network)
|
||||||
|
m.clientNetworks[id] = clientNetworkWatcher
|
||||||
|
go clientNetworkWatcher.peersStateAndUpdateWatcher()
|
||||||
|
}
|
||||||
|
update := routesUpdate{
|
||||||
|
updateSerial: updateSerial,
|
||||||
|
routes: routes,
|
||||||
|
}
|
||||||
|
|
||||||
|
clientNetworkWatcher.sendUpdateToClientNetworkWatcher(update)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DefaultManager) updateServerRoutes(routesMap map[string]*route.Route) error {
|
||||||
|
serverRoutesToRemove := make([]string, 0)
|
||||||
|
|
||||||
|
if len(routesMap) > 0 {
|
||||||
|
err := m.serverRouter.firewall.RestoreOrCreateContainers()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't initialize firewall containers, got err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for routeID := range m.serverRoutes {
|
||||||
|
update, found := routesMap[routeID]
|
||||||
|
if !found || !update.IsEqual(m.serverRoutes[routeID]) {
|
||||||
|
serverRoutesToRemove = append(serverRoutesToRemove, routeID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, routeID := range serverRoutesToRemove {
|
||||||
|
oldRoute := m.serverRoutes[routeID]
|
||||||
|
err := m.removeFromServerNetwork(oldRoute)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to remove route id: %s, network %s, from server, got: %v",
|
||||||
|
oldRoute.ID, oldRoute.Network, err)
|
||||||
|
}
|
||||||
|
delete(m.serverRoutes, routeID)
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, newRoute := range routesMap {
|
||||||
|
_, found := m.serverRoutes[id]
|
||||||
|
if found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := m.addToServerNetwork(newRoute)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to add route %s from server, got: %v", newRoute.ID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m.serverRoutes[id] = newRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.serverRoutes) > 0 {
|
||||||
|
err := enableIPForwarding()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRoutes compares received routes with existing routes and remove, update or add them to the client and server maps
|
||||||
|
func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) error {
|
||||||
|
select {
|
||||||
|
case <-m.ctx.Done():
|
||||||
|
log.Infof("not updating routes as context is closed")
|
||||||
|
return m.ctx.Err()
|
||||||
|
default:
|
||||||
|
m.mux.Lock()
|
||||||
|
defer m.mux.Unlock()
|
||||||
|
|
||||||
|
newClientRoutesIDMap := make(map[string][]*route.Route)
|
||||||
|
newServerRoutesMap := make(map[string]*route.Route)
|
||||||
|
|
||||||
|
for _, newRoute := range newRoutes {
|
||||||
|
// only linux is supported for now
|
||||||
|
if newRoute.Peer == m.pubKey {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
log.Warnf("received a route to manage, but agent doesn't support router mode on %s OS", runtime.GOOS)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newServerRoutesMap[newRoute.ID] = newRoute
|
||||||
|
} else {
|
||||||
|
// if prefix is too small, lets assume is a possible default route which is not yet supported
|
||||||
|
// we skip this route management
|
||||||
|
if newRoute.Network.Bits() < 7 {
|
||||||
|
log.Errorf("this agent version: %s, doesn't support default routes, received %s, skiping this route",
|
||||||
|
system.NetbirdVersion(), newRoute.Network)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
clientNetworkID := getClientNetworkID(newRoute)
|
||||||
|
newClientRoutesIDMap[clientNetworkID] = append(newClientRoutesIDMap[clientNetworkID], newRoute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.updateClientNetworks(updateSerial, newClientRoutesIDMap)
|
||||||
|
|
||||||
|
err := m.updateServerRoutes(newServerRoutesMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
370
client/internal/routemanager/manager_test.go
Normal file
370
client/internal/routemanager/manager_test.go
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/status"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"net/netip"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// send 5 routes, one for server and 4 for clients, one normal and 2 HA and one small
|
||||||
|
// if linux host, should have one for server in map
|
||||||
|
// we should have 2 client manager
|
||||||
|
// 2 ranges in our routing table
|
||||||
|
|
||||||
|
const localPeerKey = "local"
|
||||||
|
const remotePeerKey1 = "remote1"
|
||||||
|
const remotePeerKey2 = "remote1"
|
||||||
|
|
||||||
|
func TestManagerUpdateRoutes(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
inputInitRoutes []*route.Route
|
||||||
|
inputRoutes []*route.Route
|
||||||
|
inputSerial uint64
|
||||||
|
shouldCheckServerRoutes bool
|
||||||
|
serverRoutesExpected int
|
||||||
|
clientNetworkWatchersExpected int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Should create 2 client networks",
|
||||||
|
inputInitRoutes: []*route.Route{},
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("100.64.251.250/30"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("8.8.8.8/32"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
clientNetworkWatchersExpected: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Create 2 Server Routes",
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: localPeerKey,
|
||||||
|
Network: netip.MustParsePrefix("100.64.252.250/30"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: localPeerKey,
|
||||||
|
Network: netip.MustParsePrefix("8.8.8.9/32"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
shouldCheckServerRoutes: runtime.GOOS == "linux",
|
||||||
|
serverRoutesExpected: 2,
|
||||||
|
clientNetworkWatchersExpected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Create 1 Route For Client And Server",
|
||||||
|
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,
|
||||||
|
shouldCheckServerRoutes: runtime.GOOS == "linux",
|
||||||
|
serverRoutesExpected: 1,
|
||||||
|
clientNetworkWatchersExpected: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Create 1 HA Route and 1 Standalone",
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("8.8.20.0/24"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey2,
|
||||||
|
Network: netip.MustParsePrefix("8.8.20.0/24"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "c",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("8.8.9.9/32"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
clientNetworkWatchersExpected: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No Small Client Route Should Be Added",
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("0.0.0.0/0"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
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",
|
||||||
|
inputInitRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("100.64.251.250/30"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("8.8.8.8/32"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("100.64.251.250/30"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
clientNetworkWatchersExpected: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Update Route to HA",
|
||||||
|
inputInitRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("100.64.251.250/30"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("8.8.8.8/32"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("100.64.251.250/30"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey2,
|
||||||
|
Network: netip.MustParsePrefix("100.64.251.250/30"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputSerial: 1,
|
||||||
|
clientNetworkWatchersExpected: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Remove Client Routes",
|
||||||
|
inputInitRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("100.64.251.250/30"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("8.8.8.8/32"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputRoutes: []*route.Route{},
|
||||||
|
inputSerial: 1,
|
||||||
|
clientNetworkWatchersExpected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Remove All Routes",
|
||||||
|
inputInitRoutes: []*route.Route{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
NetID: "routeA",
|
||||||
|
Peer: localPeerKey,
|
||||||
|
Network: netip.MustParsePrefix("100.64.251.250/30"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "b",
|
||||||
|
NetID: "routeB",
|
||||||
|
Peer: remotePeerKey1,
|
||||||
|
Network: netip.MustParsePrefix("8.8.8.8/32"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputRoutes: []*route.Route{},
|
||||||
|
inputSerial: 1,
|
||||||
|
shouldCheckServerRoutes: true,
|
||||||
|
serverRoutesExpected: 0,
|
||||||
|
clientNetworkWatchersExpected: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun43%d", n), "100.65.65.2/24", iface.DefaultMTU)
|
||||||
|
require.NoError(t, err, "should create testing WGIface interface")
|
||||||
|
defer wgInterface.Close()
|
||||||
|
|
||||||
|
err = wgInterface.Create()
|
||||||
|
require.NoError(t, err, "should create testing wireguard interface")
|
||||||
|
|
||||||
|
statusRecorder := status.NewRecorder()
|
||||||
|
ctx := context.TODO()
|
||||||
|
routeManager := NewManager(ctx, localPeerKey, wgInterface, statusRecorder)
|
||||||
|
defer routeManager.Stop()
|
||||||
|
|
||||||
|
if len(testCase.inputInitRoutes) > 0 {
|
||||||
|
err = routeManager.UpdateRoutes(testCase.inputSerial, testCase.inputRoutes)
|
||||||
|
require.NoError(t, err, "should update routes with init routes")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = routeManager.UpdateRoutes(testCase.inputSerial+uint64(len(testCase.inputInitRoutes)), testCase.inputRoutes)
|
||||||
|
require.NoError(t, err, "should update routes")
|
||||||
|
|
||||||
|
require.Len(t, routeManager.clientNetworks, testCase.clientNetworkWatchersExpected, "client networks size should match")
|
||||||
|
|
||||||
|
if testCase.shouldCheckServerRoutes {
|
||||||
|
require.Len(t, routeManager.serverRoutes, testCase.serverRoutesExpected, "server networks size should match")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
27
client/internal/routemanager/mock.go
Normal file
27
client/internal/routemanager/mock.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockManager is the mock instance of a route manager
|
||||||
|
type MockManager struct {
|
||||||
|
UpdateRoutesFunc func(updateSerial uint64, newRoutes []*route.Route) error
|
||||||
|
StopFunc func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRoutes mock implementation of UpdateRoutes from Manager interface
|
||||||
|
func (m *MockManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) error {
|
||||||
|
if m.UpdateRoutesFunc != nil {
|
||||||
|
return m.UpdateRoutesFunc(updateSerial, newRoutes)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("method UpdateRoutes is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop mock implementation of Stop from Manager interface
|
||||||
|
func (m *MockManager) Stop() {
|
||||||
|
if m.StopFunc != nil {
|
||||||
|
m.StopFunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
432
client/internal/routemanager/nftables_linux.go
Normal file
432
client/internal/routemanager/nftables_linux.go
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/nftables/binaryutil"
|
||||||
|
"github.com/google/nftables/expr"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
import "github.com/google/nftables"
|
||||||
|
|
||||||
|
const (
|
||||||
|
nftablesTable = "netbird-rt"
|
||||||
|
nftablesRoutingForwardingChain = "netbird-rt-fwd"
|
||||||
|
nftablesRoutingNatChain = "netbird-rt-nat"
|
||||||
|
)
|
||||||
|
|
||||||
|
// constants needed to create nftable rules
|
||||||
|
const (
|
||||||
|
ipv4Len = 4
|
||||||
|
ipv4SrcOffset = 12
|
||||||
|
ipv4DestOffset = 16
|
||||||
|
ipv6Len = 16
|
||||||
|
ipv6SrcOffset = 8
|
||||||
|
ipv6DestOffset = 24
|
||||||
|
exprDirectionSource = "source"
|
||||||
|
exprDirectionDestination = "destination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// some presets for building nftable rules
|
||||||
|
var (
|
||||||
|
zeroXor = binaryutil.NativeEndian.PutUint32(0)
|
||||||
|
|
||||||
|
zeroXor6 = append(binaryutil.NativeEndian.PutUint64(0), binaryutil.NativeEndian.PutUint64(0)...)
|
||||||
|
|
||||||
|
exprAllowRelatedEstablished = []expr.Any{
|
||||||
|
&expr.Ct{
|
||||||
|
Register: 1,
|
||||||
|
SourceRegister: false,
|
||||||
|
Key: 0,
|
||||||
|
},
|
||||||
|
&expr.Bitwise{
|
||||||
|
DestRegister: 1,
|
||||||
|
SourceRegister: 1,
|
||||||
|
Len: 4,
|
||||||
|
Mask: []uint8{0x6, 0x0, 0x0, 0x0},
|
||||||
|
Xor: zeroXor,
|
||||||
|
},
|
||||||
|
&expr.Cmp{
|
||||||
|
Register: 1,
|
||||||
|
Data: binaryutil.NativeEndian.PutUint32(0),
|
||||||
|
},
|
||||||
|
&expr.Counter{},
|
||||||
|
&expr.Verdict{
|
||||||
|
Kind: expr.VerdictAccept,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
exprCounterAccept = []expr.Any{
|
||||||
|
&expr.Counter{},
|
||||||
|
&expr.Verdict{
|
||||||
|
Kind: expr.VerdictAccept,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type nftablesManager struct {
|
||||||
|
ctx context.Context
|
||||||
|
stop context.CancelFunc
|
||||||
|
conn *nftables.Conn
|
||||||
|
tableIPv4 *nftables.Table
|
||||||
|
tableIPv6 *nftables.Table
|
||||||
|
chains map[string]map[string]*nftables.Chain
|
||||||
|
rules map[string]*nftables.Rule
|
||||||
|
mux sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanRoutingRules cleans existing nftables rules from the system
|
||||||
|
func (n *nftablesManager) CleanRoutingRules() {
|
||||||
|
n.mux.Lock()
|
||||||
|
defer n.mux.Unlock()
|
||||||
|
log.Debug("flushing tables")
|
||||||
|
if n.tableIPv4 != nil && n.tableIPv6 != nil {
|
||||||
|
n.conn.FlushTable(n.tableIPv6)
|
||||||
|
n.conn.FlushTable(n.tableIPv4)
|
||||||
|
}
|
||||||
|
log.Debugf("flushing tables result in: %v error", n.conn.Flush())
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreOrCreateContainers restores existing nftables containers (tables and chains)
|
||||||
|
// if they don't exist, we create them
|
||||||
|
func (n *nftablesManager) RestoreOrCreateContainers() error {
|
||||||
|
n.mux.Lock()
|
||||||
|
defer n.mux.Unlock()
|
||||||
|
|
||||||
|
if n.tableIPv6 != nil && n.tableIPv4 != nil {
|
||||||
|
log.Debugf("nftables: containers already restored, skipping")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tables, err := n.conn.ListTables()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("nftables: unable to list tables: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, table := range tables {
|
||||||
|
if table.Name == nftablesTable {
|
||||||
|
if table.Family == nftables.TableFamilyIPv4 {
|
||||||
|
n.tableIPv4 = table
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n.tableIPv6 = table
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.tableIPv4 == nil {
|
||||||
|
n.tableIPv4 = n.conn.AddTable(&nftables.Table{
|
||||||
|
Name: nftablesTable,
|
||||||
|
Family: nftables.TableFamilyIPv4,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.tableIPv6 == nil {
|
||||||
|
n.tableIPv6 = n.conn.AddTable(&nftables.Table{
|
||||||
|
Name: nftablesTable,
|
||||||
|
Family: nftables.TableFamilyIPv6,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
chains, err := n.conn.ListChains()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("nftables: unable to list chains: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n.chains[ipv4] = make(map[string]*nftables.Chain)
|
||||||
|
n.chains[ipv6] = make(map[string]*nftables.Chain)
|
||||||
|
|
||||||
|
for _, chain := range chains {
|
||||||
|
switch {
|
||||||
|
case chain.Table.Name == nftablesTable && chain.Table.Family == nftables.TableFamilyIPv4:
|
||||||
|
n.chains[ipv4][chain.Name] = chain
|
||||||
|
case chain.Table.Name == nftablesTable && chain.Table.Family == nftables.TableFamilyIPv6:
|
||||||
|
n.chains[ipv6][chain.Name] = chain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found := n.chains[ipv4][nftablesRoutingForwardingChain]; !found {
|
||||||
|
n.chains[ipv4][nftablesRoutingForwardingChain] = n.conn.AddChain(&nftables.Chain{
|
||||||
|
Name: nftablesRoutingForwardingChain,
|
||||||
|
Table: n.tableIPv4,
|
||||||
|
Hooknum: nftables.ChainHookForward,
|
||||||
|
Priority: nftables.ChainPriorityNATDest + 1,
|
||||||
|
Type: nftables.ChainTypeFilter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found := n.chains[ipv4][nftablesRoutingNatChain]; !found {
|
||||||
|
n.chains[ipv4][nftablesRoutingNatChain] = n.conn.AddChain(&nftables.Chain{
|
||||||
|
Name: nftablesRoutingNatChain,
|
||||||
|
Table: n.tableIPv4,
|
||||||
|
Hooknum: nftables.ChainHookPostrouting,
|
||||||
|
Priority: nftables.ChainPriorityNATSource - 1,
|
||||||
|
Type: nftables.ChainTypeNAT,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found := n.chains[ipv6][nftablesRoutingForwardingChain]; !found {
|
||||||
|
n.chains[ipv6][nftablesRoutingForwardingChain] = n.conn.AddChain(&nftables.Chain{
|
||||||
|
Name: nftablesRoutingForwardingChain,
|
||||||
|
Table: n.tableIPv6,
|
||||||
|
Hooknum: nftables.ChainHookForward,
|
||||||
|
Priority: nftables.ChainPriorityNATDest + 1,
|
||||||
|
Type: nftables.ChainTypeFilter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found := n.chains[ipv6][nftablesRoutingNatChain]; !found {
|
||||||
|
n.chains[ipv6][nftablesRoutingNatChain] = n.conn.AddChain(&nftables.Chain{
|
||||||
|
Name: nftablesRoutingNatChain,
|
||||||
|
Table: n.tableIPv6,
|
||||||
|
Hooknum: nftables.ChainHookPostrouting,
|
||||||
|
Priority: nftables.ChainPriorityNATSource - 1,
|
||||||
|
Type: nftables.ChainTypeNAT,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err = n.refreshRulesMap()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n.checkOrCreateDefaultForwardingRules()
|
||||||
|
err = n.conn.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("nftables: unable to initialize table: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// refreshRulesMap refreshes the rule map with the latest rules. this is useful to avoid
|
||||||
|
// duplicates and to get missing attributes that we don't have when adding new rules
|
||||||
|
func (n *nftablesManager) refreshRulesMap() error {
|
||||||
|
for _, registeredChains := range n.chains {
|
||||||
|
for _, chain := range registeredChains {
|
||||||
|
rules, err := n.conn.GetRules(chain.Table, chain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("nftables: unable to list rules: %v", err)
|
||||||
|
}
|
||||||
|
for _, rule := range rules {
|
||||||
|
if len(rule.UserData) > 0 {
|
||||||
|
n.rules[string(rule.UserData)] = rule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkOrCreateDefaultForwardingRules checks if the default forwarding rules are enabled
|
||||||
|
func (n *nftablesManager) checkOrCreateDefaultForwardingRules() {
|
||||||
|
_, foundIPv4 := n.rules[ipv4Forwarding]
|
||||||
|
if !foundIPv4 {
|
||||||
|
n.rules[ipv4Forwarding] = n.conn.AddRule(&nftables.Rule{
|
||||||
|
Table: n.tableIPv4,
|
||||||
|
Chain: n.chains[ipv4][nftablesRoutingForwardingChain],
|
||||||
|
Exprs: exprAllowRelatedEstablished,
|
||||||
|
UserData: []byte(ipv4Forwarding),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_, foundIPv6 := n.rules[ipv6Forwarding]
|
||||||
|
if !foundIPv6 {
|
||||||
|
n.rules[ipv6Forwarding] = n.conn.AddRule(&nftables.Rule{
|
||||||
|
Table: n.tableIPv6,
|
||||||
|
Chain: n.chains[ipv6][nftablesRoutingForwardingChain],
|
||||||
|
Exprs: exprAllowRelatedEstablished,
|
||||||
|
UserData: []byte(ipv6Forwarding),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertRoutingRules inserts a nftable rule pair to the forwarding chain and if enabled, to the nat chain
|
||||||
|
func (n *nftablesManager) InsertRoutingRules(pair routerPair) error {
|
||||||
|
n.mux.Lock()
|
||||||
|
defer n.mux.Unlock()
|
||||||
|
|
||||||
|
err := n.refreshRulesMap()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = n.insertRoutingRule(forwardingFormat, nftablesRoutingForwardingChain, pair, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = n.insertRoutingRule(inForwardingFormat, nftablesRoutingForwardingChain, getInPair(pair), false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pair.masquerade {
|
||||||
|
err = n.insertRoutingRule(natFormat, nftablesRoutingNatChain, pair, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = n.insertRoutingRule(inNatFormat, nftablesRoutingNatChain, getInPair(pair), true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = n.conn.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("nftables: unable to insert rules for %s: %v", pair.destination, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertRoutingRule inserts a nftable rule to the conn client flush queue
|
||||||
|
func (n *nftablesManager) insertRoutingRule(format, chain string, pair routerPair, isNat bool) error {
|
||||||
|
|
||||||
|
prefix := netip.MustParsePrefix(pair.source)
|
||||||
|
|
||||||
|
sourceExp := generateCIDRMatcherExpressions("source", pair.source)
|
||||||
|
destExp := generateCIDRMatcherExpressions("destination", pair.destination)
|
||||||
|
|
||||||
|
var expression []expr.Any
|
||||||
|
if isNat {
|
||||||
|
expression = append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...)
|
||||||
|
} else {
|
||||||
|
expression = append(sourceExp, append(destExp, exprCounterAccept...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleKey := genKey(format, pair.ID)
|
||||||
|
|
||||||
|
_, exists := n.rules[ruleKey]
|
||||||
|
if exists {
|
||||||
|
err := n.removeRoutingRule(format, pair)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix.Addr().Unmap().Is4() {
|
||||||
|
n.rules[ruleKey] = n.conn.InsertRule(&nftables.Rule{
|
||||||
|
Table: n.tableIPv4,
|
||||||
|
Chain: n.chains[ipv4][chain],
|
||||||
|
Exprs: expression,
|
||||||
|
UserData: []byte(ruleKey),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
n.rules[ruleKey] = n.conn.InsertRule(&nftables.Rule{
|
||||||
|
Table: n.tableIPv6,
|
||||||
|
Chain: n.chains[ipv6][chain],
|
||||||
|
Exprs: expression,
|
||||||
|
UserData: []byte(ruleKey),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRoutingRules removes a nftable rule pair from forwarding and nat chains
|
||||||
|
func (n *nftablesManager) RemoveRoutingRules(pair routerPair) error {
|
||||||
|
n.mux.Lock()
|
||||||
|
defer n.mux.Unlock()
|
||||||
|
|
||||||
|
err := n.refreshRulesMap()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = n.removeRoutingRule(forwardingFormat, pair)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = n.removeRoutingRule(inForwardingFormat, getInPair(pair))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = n.removeRoutingRule(natFormat, pair)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = n.removeRoutingRule(inNatFormat, getInPair(pair))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = n.conn.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("nftables: received error while applying rule removal for %s: %v", pair.destination, err)
|
||||||
|
}
|
||||||
|
log.Debugf("nftables: removed rules for %s", pair.destination)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeRoutingRule add a nftable rule to the removal queue and delete from rules map
|
||||||
|
func (n *nftablesManager) removeRoutingRule(format string, pair routerPair) error {
|
||||||
|
ruleKey := genKey(format, pair.ID)
|
||||||
|
|
||||||
|
rule, found := n.rules[ruleKey]
|
||||||
|
if found {
|
||||||
|
ruleType := "forwarding"
|
||||||
|
if rule.Chain.Type == nftables.ChainTypeNAT {
|
||||||
|
ruleType = "nat"
|
||||||
|
}
|
||||||
|
|
||||||
|
err := n.conn.DelRule(rule)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("nftables: unable to remove %s rule for %s: %v", ruleType, pair.destination, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("nftables: removing %s rule for %s", ruleType, pair.destination)
|
||||||
|
|
||||||
|
delete(n.rules, ruleKey)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPayloadDirectives get expression directives based on ip version and direction
|
||||||
|
func getPayloadDirectives(direction string, isIPv4 bool, isIPv6 bool) (uint32, uint32, []byte) {
|
||||||
|
switch {
|
||||||
|
case direction == exprDirectionSource && isIPv4:
|
||||||
|
return ipv4SrcOffset, ipv4Len, zeroXor
|
||||||
|
case direction == exprDirectionDestination && isIPv4:
|
||||||
|
return ipv4DestOffset, ipv4Len, zeroXor
|
||||||
|
case direction == exprDirectionSource && isIPv6:
|
||||||
|
return ipv6SrcOffset, ipv6Len, zeroXor6
|
||||||
|
case direction == exprDirectionDestination && isIPv6:
|
||||||
|
return ipv6DestOffset, ipv6Len, zeroXor6
|
||||||
|
default:
|
||||||
|
panic("no matched payload directive")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateCIDRMatcherExpressions generates nftables expressions that matches a CIDR
|
||||||
|
func generateCIDRMatcherExpressions(direction string, cidr string) []expr.Any {
|
||||||
|
ip, network, _ := net.ParseCIDR(cidr)
|
||||||
|
ipToAdd, _ := netip.AddrFromSlice(ip)
|
||||||
|
add := ipToAdd.Unmap()
|
||||||
|
|
||||||
|
offSet, packetLen, zeroXor := getPayloadDirectives(direction, add.Is4(), add.Is6())
|
||||||
|
|
||||||
|
return []expr.Any{
|
||||||
|
// fetch src add
|
||||||
|
&expr.Payload{
|
||||||
|
DestRegister: 1,
|
||||||
|
Base: expr.PayloadBaseNetworkHeader,
|
||||||
|
Offset: offSet,
|
||||||
|
Len: packetLen,
|
||||||
|
},
|
||||||
|
// net mask
|
||||||
|
&expr.Bitwise{
|
||||||
|
DestRegister: 1,
|
||||||
|
SourceRegister: 1,
|
||||||
|
Len: packetLen,
|
||||||
|
Mask: network.Mask,
|
||||||
|
Xor: zeroXor,
|
||||||
|
},
|
||||||
|
// net address
|
||||||
|
&expr.Cmp{
|
||||||
|
Register: 1,
|
||||||
|
Data: add.AsSlice(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
333
client/internal/routemanager/nftables_linux_test.go
Normal file
333
client/internal/routemanager/nftables_linux_test.go
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/google/nftables"
|
||||||
|
"github.com/google/nftables/expr"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNftablesManager_RestoreOrCreateContainers(t *testing.T) {
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
|
|
||||||
|
manager := &nftablesManager{
|
||||||
|
ctx: ctx,
|
||||||
|
stop: cancel,
|
||||||
|
conn: &nftables.Conn{},
|
||||||
|
chains: make(map[string]map[string]*nftables.Chain),
|
||||||
|
rules: make(map[string]*nftables.Rule),
|
||||||
|
}
|
||||||
|
|
||||||
|
nftablesTestingClient := &nftables.Conn{}
|
||||||
|
|
||||||
|
defer manager.CleanRoutingRules()
|
||||||
|
|
||||||
|
err := manager.RestoreOrCreateContainers()
|
||||||
|
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[ipv4], 2, "should have created chains for ipv4")
|
||||||
|
require.Len(t, manager.chains[ipv4], 2, "should have created chains for ipv6")
|
||||||
|
require.Len(t, manager.rules, 2, "should have created rules for ipv4 and ipv6")
|
||||||
|
|
||||||
|
pair := routerPair{
|
||||||
|
ID: "abc",
|
||||||
|
source: "100.100.100.1/32",
|
||||||
|
destination: "100.100.100.0/24",
|
||||||
|
masquerade: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceExp := generateCIDRMatcherExpressions("source", pair.source)
|
||||||
|
destExp := generateCIDRMatcherExpressions("destination", pair.destination)
|
||||||
|
|
||||||
|
forward4Exp := append(sourceExp, append(destExp, exprCounterAccept...)...)
|
||||||
|
forward4RuleKey := genKey(forwardingFormat, pair.ID)
|
||||||
|
inserted4Forwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
|
Table: manager.tableIPv4,
|
||||||
|
Chain: manager.chains[ipv4][nftablesRoutingForwardingChain],
|
||||||
|
Exprs: forward4Exp,
|
||||||
|
UserData: []byte(forward4RuleKey),
|
||||||
|
})
|
||||||
|
|
||||||
|
nat4Exp := append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...)
|
||||||
|
nat4RuleKey := genKey(natFormat, pair.ID)
|
||||||
|
|
||||||
|
inserted4Nat := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
|
Table: manager.tableIPv4,
|
||||||
|
Chain: manager.chains[ipv4][nftablesRoutingNatChain],
|
||||||
|
Exprs: nat4Exp,
|
||||||
|
UserData: []byte(nat4RuleKey),
|
||||||
|
})
|
||||||
|
|
||||||
|
err = nftablesTestingClient.Flush()
|
||||||
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
pair = routerPair{
|
||||||
|
ID: "xyz",
|
||||||
|
source: "fc00::1/128",
|
||||||
|
destination: "fc11::/64",
|
||||||
|
masquerade: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceExp = generateCIDRMatcherExpressions("source", pair.source)
|
||||||
|
destExp = generateCIDRMatcherExpressions("destination", pair.destination)
|
||||||
|
|
||||||
|
forward6Exp := append(sourceExp, append(destExp, exprCounterAccept...)...)
|
||||||
|
forward6RuleKey := genKey(forwardingFormat, pair.ID)
|
||||||
|
inserted6Forwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
|
Table: manager.tableIPv6,
|
||||||
|
Chain: manager.chains[ipv6][nftablesRoutingForwardingChain],
|
||||||
|
Exprs: forward6Exp,
|
||||||
|
UserData: []byte(forward6RuleKey),
|
||||||
|
})
|
||||||
|
|
||||||
|
nat6Exp := append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...)
|
||||||
|
nat6RuleKey := genKey(natFormat, pair.ID)
|
||||||
|
|
||||||
|
inserted6Nat := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
|
Table: manager.tableIPv6,
|
||||||
|
Chain: manager.chains[ipv6][nftablesRoutingNatChain],
|
||||||
|
Exprs: nat6Exp,
|
||||||
|
UserData: []byte(nat6RuleKey),
|
||||||
|
})
|
||||||
|
|
||||||
|
err = nftablesTestingClient.Flush()
|
||||||
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
manager.tableIPv4 = nil
|
||||||
|
manager.tableIPv6 = nil
|
||||||
|
|
||||||
|
err = manager.RestoreOrCreateContainers()
|
||||||
|
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[ipv4], 2, "should have created chains for ipv4")
|
||||||
|
require.Len(t, manager.chains[ipv4], 2, "should have created chains for ipv6")
|
||||||
|
require.Len(t, manager.rules, 6, "should have restored all rules for ipv4 and ipv6")
|
||||||
|
|
||||||
|
foundRule, found := manager.rules[forward4RuleKey]
|
||||||
|
require.True(t, found, "forwarding rule should exist in the map")
|
||||||
|
assert.Equal(t, inserted4Forwarding.Exprs, foundRule.Exprs, "stored forwarding rule expressions should match")
|
||||||
|
|
||||||
|
foundRule, found = manager.rules[nat4RuleKey]
|
||||||
|
require.True(t, found, "nat rule should exist in the map")
|
||||||
|
// match len of output as nftables client doesn't return expressions with masquerade expression
|
||||||
|
assert.ElementsMatch(t, inserted4Nat.Exprs[:len(foundRule.Exprs)], foundRule.Exprs, "stored nat rule expressions should match")
|
||||||
|
|
||||||
|
foundRule, found = manager.rules[forward6RuleKey]
|
||||||
|
require.True(t, found, "forwarding rule should exist in the map")
|
||||||
|
assert.Equal(t, inserted6Forwarding.Exprs, foundRule.Exprs, "stored forward rule should match")
|
||||||
|
|
||||||
|
foundRule, found = manager.rules[nat6RuleKey]
|
||||||
|
require.True(t, found, "nat rule should exist in the map")
|
||||||
|
// match len of output as nftables client doesn't return expressions with masquerade expression
|
||||||
|
assert.ElementsMatch(t, inserted6Nat.Exprs[:len(foundRule.Exprs)], foundRule.Exprs, "stored nat rule should match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNftablesManager_InsertRoutingRules(t *testing.T) {
|
||||||
|
|
||||||
|
for _, testCase := range insertRuleTestCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
|
|
||||||
|
manager := &nftablesManager{
|
||||||
|
ctx: ctx,
|
||||||
|
stop: cancel,
|
||||||
|
conn: &nftables.Conn{},
|
||||||
|
chains: make(map[string]map[string]*nftables.Chain),
|
||||||
|
rules: make(map[string]*nftables.Rule),
|
||||||
|
}
|
||||||
|
|
||||||
|
nftablesTestingClient := &nftables.Conn{}
|
||||||
|
|
||||||
|
defer manager.CleanRoutingRules()
|
||||||
|
|
||||||
|
err := manager.RestoreOrCreateContainers()
|
||||||
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
err = manager.InsertRoutingRules(testCase.inputPair)
|
||||||
|
require.NoError(t, err, "forwarding pair should be inserted")
|
||||||
|
|
||||||
|
sourceExp := generateCIDRMatcherExpressions("source", testCase.inputPair.source)
|
||||||
|
destExp := generateCIDRMatcherExpressions("destination", testCase.inputPair.destination)
|
||||||
|
testingExpression := append(sourceExp, destExp...)
|
||||||
|
fwdRuleKey := genKey(forwardingFormat, testCase.inputPair.ID)
|
||||||
|
|
||||||
|
found := 0
|
||||||
|
for _, registeredChains := range manager.chains {
|
||||||
|
for _, chain := range registeredChains {
|
||||||
|
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
||||||
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
|
for _, rule := range rules {
|
||||||
|
if len(rule.UserData) > 0 && string(rule.UserData) == fwdRuleKey {
|
||||||
|
require.ElementsMatchf(t, rule.Exprs[:len(testingExpression)], testingExpression, "forwarding rule elements should match")
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, 1, found, "should find at least 1 rule to test")
|
||||||
|
|
||||||
|
if testCase.inputPair.masquerade {
|
||||||
|
natRuleKey := genKey(natFormat, testCase.inputPair.ID)
|
||||||
|
found := 0
|
||||||
|
for _, registeredChains := range manager.chains {
|
||||||
|
for _, chain := range registeredChains {
|
||||||
|
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
||||||
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
|
for _, rule := range rules {
|
||||||
|
if len(rule.UserData) > 0 && string(rule.UserData) == natRuleKey {
|
||||||
|
require.ElementsMatchf(t, rule.Exprs[:len(testingExpression)], testingExpression, "nat rule elements should match")
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Equal(t, 1, found, "should find at least 1 rule to test")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceExp = generateCIDRMatcherExpressions("source", getInPair(testCase.inputPair).source)
|
||||||
|
destExp = generateCIDRMatcherExpressions("destination", getInPair(testCase.inputPair).destination)
|
||||||
|
testingExpression = append(sourceExp, destExp...)
|
||||||
|
inFwdRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
|
||||||
|
|
||||||
|
found = 0
|
||||||
|
for _, registeredChains := range manager.chains {
|
||||||
|
for _, chain := range registeredChains {
|
||||||
|
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
||||||
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
|
for _, rule := range rules {
|
||||||
|
if len(rule.UserData) > 0 && string(rule.UserData) == inFwdRuleKey {
|
||||||
|
require.ElementsMatchf(t, rule.Exprs[:len(testingExpression)], testingExpression, "income forwarding rule elements should match")
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, 1, found, "should find at least 1 rule to test")
|
||||||
|
|
||||||
|
if testCase.inputPair.masquerade {
|
||||||
|
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
|
||||||
|
found := 0
|
||||||
|
for _, registeredChains := range manager.chains {
|
||||||
|
for _, chain := range registeredChains {
|
||||||
|
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
||||||
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
|
for _, rule := range rules {
|
||||||
|
if len(rule.UserData) > 0 && string(rule.UserData) == inNatRuleKey {
|
||||||
|
require.ElementsMatchf(t, rule.Exprs[:len(testingExpression)], testingExpression, "income nat rule elements should match")
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Equal(t, 1, found, "should find at least 1 rule to test")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
|
||||||
|
|
||||||
|
for _, testCase := range removeRuleTestCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
|
|
||||||
|
manager := &nftablesManager{
|
||||||
|
ctx: ctx,
|
||||||
|
stop: cancel,
|
||||||
|
conn: &nftables.Conn{},
|
||||||
|
chains: make(map[string]map[string]*nftables.Chain),
|
||||||
|
rules: make(map[string]*nftables.Rule),
|
||||||
|
}
|
||||||
|
|
||||||
|
nftablesTestingClient := &nftables.Conn{}
|
||||||
|
|
||||||
|
defer manager.CleanRoutingRules()
|
||||||
|
|
||||||
|
err := manager.RestoreOrCreateContainers()
|
||||||
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
table := manager.tableIPv4
|
||||||
|
if testCase.ipVersion == ipv6 {
|
||||||
|
table = manager.tableIPv6
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceExp := generateCIDRMatcherExpressions("source", testCase.inputPair.source)
|
||||||
|
destExp := generateCIDRMatcherExpressions("destination", testCase.inputPair.destination)
|
||||||
|
|
||||||
|
forwardExp := append(sourceExp, append(destExp, exprCounterAccept...)...)
|
||||||
|
forwardRuleKey := genKey(forwardingFormat, testCase.inputPair.ID)
|
||||||
|
insertedForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: manager.chains[testCase.ipVersion][nftablesRoutingForwardingChain],
|
||||||
|
Exprs: forwardExp,
|
||||||
|
UserData: []byte(forwardRuleKey),
|
||||||
|
})
|
||||||
|
|
||||||
|
natExp := append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...)
|
||||||
|
natRuleKey := genKey(natFormat, testCase.inputPair.ID)
|
||||||
|
|
||||||
|
insertedNat := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: manager.chains[testCase.ipVersion][nftablesRoutingNatChain],
|
||||||
|
Exprs: natExp,
|
||||||
|
UserData: []byte(natRuleKey),
|
||||||
|
})
|
||||||
|
|
||||||
|
sourceExp = generateCIDRMatcherExpressions("source", getInPair(testCase.inputPair).source)
|
||||||
|
destExp = generateCIDRMatcherExpressions("destination", getInPair(testCase.inputPair).destination)
|
||||||
|
|
||||||
|
forwardExp = append(sourceExp, append(destExp, exprCounterAccept...)...)
|
||||||
|
inForwardRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
|
||||||
|
insertedInForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: manager.chains[testCase.ipVersion][nftablesRoutingForwardingChain],
|
||||||
|
Exprs: forwardExp,
|
||||||
|
UserData: []byte(inForwardRuleKey),
|
||||||
|
})
|
||||||
|
|
||||||
|
natExp = append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...)
|
||||||
|
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
|
||||||
|
|
||||||
|
insertedInNat := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: manager.chains[testCase.ipVersion][nftablesRoutingNatChain],
|
||||||
|
Exprs: natExp,
|
||||||
|
UserData: []byte(inNatRuleKey),
|
||||||
|
})
|
||||||
|
|
||||||
|
err = nftablesTestingClient.Flush()
|
||||||
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
manager.tableIPv4 = nil
|
||||||
|
manager.tableIPv6 = nil
|
||||||
|
|
||||||
|
err = manager.RestoreOrCreateContainers()
|
||||||
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
err = manager.RemoveRoutingRules(testCase.inputPair)
|
||||||
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
for _, registeredChains := range manager.chains {
|
||||||
|
for _, chain := range registeredChains {
|
||||||
|
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
||||||
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
|
for _, rule := range rules {
|
||||||
|
if len(rule.UserData) > 0 {
|
||||||
|
require.NotEqual(t, insertedForwarding.UserData, rule.UserData, "forwarding rule should not exist")
|
||||||
|
require.NotEqual(t, insertedNat.UserData, rule.UserData, "nat rule should not exist")
|
||||||
|
require.NotEqual(t, insertedInForwarding.UserData, rule.UserData, "income forwarding rule should not exist")
|
||||||
|
require.NotEqual(t, insertedInNat.UserData, rule.UserData, "income nat rule should not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
67
client/internal/routemanager/server.go
Normal file
67
client/internal/routemanager/server.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serverRouter struct {
|
||||||
|
routes map[string]*route.Route
|
||||||
|
// best effort to keep net forward configuration as it was
|
||||||
|
netForwardHistoryEnabled bool
|
||||||
|
mux sync.Mutex
|
||||||
|
firewall firewallManager
|
||||||
|
}
|
||||||
|
|
||||||
|
type routerPair struct {
|
||||||
|
ID string
|
||||||
|
source string
|
||||||
|
destination string
|
||||||
|
masquerade bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func routeToRouterPair(source string, route *route.Route) routerPair {
|
||||||
|
parsed := netip.MustParsePrefix(source).Masked()
|
||||||
|
return routerPair{
|
||||||
|
ID: route.ID,
|
||||||
|
source: parsed.String(),
|
||||||
|
destination: route.Network.Masked().String(),
|
||||||
|
masquerade: route.Masquerade,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DefaultManager) removeFromServerNetwork(route *route.Route) error {
|
||||||
|
select {
|
||||||
|
case <-m.ctx.Done():
|
||||||
|
log.Infof("not removing from server network because context is done")
|
||||||
|
return m.ctx.Err()
|
||||||
|
default:
|
||||||
|
m.serverRouter.mux.Lock()
|
||||||
|
defer m.serverRouter.mux.Unlock()
|
||||||
|
err := m.serverRouter.firewall.RemoveRoutingRules(routeToRouterPair(m.wgInterface.Address.String(), route))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(m.serverRouter.routes, route.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DefaultManager) addToServerNetwork(route *route.Route) error {
|
||||||
|
select {
|
||||||
|
case <-m.ctx.Done():
|
||||||
|
log.Infof("not adding to server network because context is done")
|
||||||
|
return m.ctx.Err()
|
||||||
|
default:
|
||||||
|
m.serverRouter.mux.Lock()
|
||||||
|
defer m.serverRouter.mux.Unlock()
|
||||||
|
err := m.serverRouter.firewall.InsertRoutingRules(routeToRouterPair(m.wgInterface.Address.String(), route))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.serverRouter.routes[route.ID] = route
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
55
client/internal/routemanager/systemops.go
Normal file
55
client/internal/routemanager/systemops.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/libp2p/go-netroute"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errRouteNotFound = fmt.Errorf("route not found")
|
||||||
|
|
||||||
|
func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error {
|
||||||
|
gateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
|
||||||
|
if err != nil && err != errRouteNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
prefixGateway, err := getExistingRIBRouteGateway(prefix)
|
||||||
|
if err != nil && err != errRouteNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefixGateway != nil && !prefixGateway.Equal(gateway) {
|
||||||
|
log.Warnf("route for network %s already exist and is pointing to the gateway: %s, won't add another one", prefix, prefixGateway)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return addToRouteTable(prefix, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string) error {
|
||||||
|
addrIP := net.ParseIP(addr)
|
||||||
|
prefixGateway, err := getExistingRIBRouteGateway(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if prefixGateway != nil && !prefixGateway.Equal(addrIP) {
|
||||||
|
log.Warnf("route for network %s is pointing to a different gateway: %s, should be pointing to: %s, not removing", prefix, prefixGateway, addrIP)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return removeFromRouteTable(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExistingRIBRouteGateway(prefix netip.Prefix) (net.IP, error) {
|
||||||
|
r, err := netroute.New()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, _, localGatewayAddress, err := r.Route(prefix.Addr().AsSlice())
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("getting routes returned an error: %v", err)
|
||||||
|
return nil, errRouteNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return localGatewayAddress, nil
|
||||||
|
}
|
||||||
73
client/internal/routemanager/systemops_linux.go
Normal file
73
client/internal/routemanager/systemops_linux.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ipv4ForwardingPath = "/proc/sys/net/ipv4/ip_forward"
|
||||||
|
|
||||||
|
func addToRouteTable(prefix netip.Prefix, addr string) error {
|
||||||
|
_, ipNet, err := net.ParseCIDR(prefix.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
addrMask := "/32"
|
||||||
|
if prefix.Addr().Unmap().Is6() {
|
||||||
|
addrMask = "/128"
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, _, err := net.ParseCIDR(addr + addrMask)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
route := &netlink.Route{
|
||||||
|
Scope: netlink.SCOPE_UNIVERSE,
|
||||||
|
Dst: ipNet,
|
||||||
|
Gw: ip,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = netlink.RouteAdd(route)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFromRouteTable(prefix netip.Prefix) error {
|
||||||
|
_, ipNet, err := net.ParseCIDR(prefix.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
route := &netlink.Route{
|
||||||
|
Scope: netlink.SCOPE_UNIVERSE,
|
||||||
|
Dst: ipNet,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = netlink.RouteDel(route)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableIPForwarding() error {
|
||||||
|
err := ioutil.WriteFile(ipv4ForwardingPath, []byte("1"), 0644)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNetForwardHistoryEnabled() bool {
|
||||||
|
out, err := ioutil.ReadFile(ipv4ForwardingPath)
|
||||||
|
if err != nil {
|
||||||
|
// todo
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(out) == "1"
|
||||||
|
}
|
||||||
41
client/internal/routemanager/systemops_nonlinux.go
Normal file
41
client/internal/routemanager/systemops_nonlinux.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/netip"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addToRouteTable(prefix netip.Prefix, addr string) error {
|
||||||
|
cmd := exec.Command("route", "add", prefix.String(), addr)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf(string(out))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFromRouteTable(prefix netip.Prefix) error {
|
||||||
|
cmd := exec.Command("route", "delete", prefix.String())
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf(string(out))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableIPForwarding() error {
|
||||||
|
log.Infof("enable IP forwarding is not implemented on %s", runtime.GOOS)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNetForwardHistoryEnabled() bool {
|
||||||
|
log.Infof("check netforwad history is not implemented on %s", runtime.GOOS)
|
||||||
|
return false
|
||||||
|
}
|
||||||
68
client/internal/routemanager/systemops_test.go
Normal file
68
client/internal/routemanager/systemops_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddRemoveRoutes(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
prefix netip.Prefix
|
||||||
|
shouldRouteToWireguard bool
|
||||||
|
shouldBeRemoved bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Should Add And Remove Route",
|
||||||
|
prefix: netip.MustParsePrefix("100.66.120.0/24"),
|
||||||
|
shouldRouteToWireguard: true,
|
||||||
|
shouldBeRemoved: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Not Add Or Remove Route",
|
||||||
|
prefix: netip.MustParsePrefix("127.0.0.1/32"),
|
||||||
|
shouldRouteToWireguard: false,
|
||||||
|
shouldBeRemoved: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", iface.DefaultMTU)
|
||||||
|
require.NoError(t, err, "should create testing WGIface interface")
|
||||||
|
defer wgInterface.Close()
|
||||||
|
|
||||||
|
err = wgInterface.Create()
|
||||||
|
require.NoError(t, err, "should create testing wireguard interface")
|
||||||
|
|
||||||
|
err = addToRouteTableIfNoExists(testCase.prefix, wgInterface.GetAddress().IP.String())
|
||||||
|
require.NoError(t, err, "should not return err")
|
||||||
|
|
||||||
|
prefixGateway, err := getExistingRIBRouteGateway(testCase.prefix)
|
||||||
|
require.NoError(t, err, "should not return err")
|
||||||
|
if testCase.shouldRouteToWireguard {
|
||||||
|
require.Equal(t, wgInterface.GetAddress().IP.String(), prefixGateway.String(), "route should point to wireguard interface IP")
|
||||||
|
} else {
|
||||||
|
require.NotEqual(t, wgInterface.GetAddress().IP.String(), prefixGateway.String(), "route should point to a different interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = removeFromRouteTableIfNonSystem(testCase.prefix, wgInterface.GetAddress().IP.String())
|
||||||
|
require.NoError(t, err, "should not return err")
|
||||||
|
|
||||||
|
prefixGateway, err = getExistingRIBRouteGateway(testCase.prefix)
|
||||||
|
require.NoError(t, err, "should not return err")
|
||||||
|
|
||||||
|
internetGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if testCase.shouldBeRemoved {
|
||||||
|
require.Equal(t, internetGateway, prefixGateway, "route should be pointing to default internet gateway")
|
||||||
|
} else {
|
||||||
|
require.NotEqual(t, internetGateway, prefixGateway, "route should be pointing to a different gateway than the internet gateway")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/cmd"
|
"github.com/netbirdio/netbird/client/cmd"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.21.2
|
// protoc v3.12.4
|
||||||
// source: daemon.proto
|
// source: daemon.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "github.com/golang/protobuf/protoc-gen-go/descriptor"
|
||||||
|
timestamp "github.com/golang/protobuf/ptypes/timestamp"
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
_ "google.golang.org/protobuf/types/descriptorpb"
|
|
||||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
)
|
)
|
||||||
@@ -384,6 +384,8 @@ type StatusResponse struct {
|
|||||||
// status of the server.
|
// status of the server.
|
||||||
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
|
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
|
||||||
FullStatus *FullStatus `protobuf:"bytes,2,opt,name=fullStatus,proto3" json:"fullStatus,omitempty"`
|
FullStatus *FullStatus `protobuf:"bytes,2,opt,name=fullStatus,proto3" json:"fullStatus,omitempty"`
|
||||||
|
// NetBird daemon version
|
||||||
|
DaemonVersion string `protobuf:"bytes,3,opt,name=daemonVersion,proto3" json:"daemonVersion,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StatusResponse) Reset() {
|
func (x *StatusResponse) Reset() {
|
||||||
@@ -432,6 +434,13 @@ func (x *StatusResponse) GetFullStatus() *FullStatus {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *StatusResponse) GetDaemonVersion() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.DaemonVersion
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type DownRequest struct {
|
type DownRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -636,14 +645,14 @@ type PeerState struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
IP string `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"`
|
IP string `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"`
|
||||||
PubKey string `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
|
PubKey string `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
|
||||||
ConnStatus string `protobuf:"bytes,3,opt,name=connStatus,proto3" json:"connStatus,omitempty"`
|
ConnStatus string `protobuf:"bytes,3,opt,name=connStatus,proto3" json:"connStatus,omitempty"`
|
||||||
ConnStatusUpdate *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=connStatusUpdate,proto3" json:"connStatusUpdate,omitempty"`
|
ConnStatusUpdate *timestamp.Timestamp `protobuf:"bytes,4,opt,name=connStatusUpdate,proto3" json:"connStatusUpdate,omitempty"`
|
||||||
Relayed bool `protobuf:"varint,5,opt,name=relayed,proto3" json:"relayed,omitempty"`
|
Relayed bool `protobuf:"varint,5,opt,name=relayed,proto3" json:"relayed,omitempty"`
|
||||||
Direct bool `protobuf:"varint,6,opt,name=direct,proto3" json:"direct,omitempty"`
|
Direct bool `protobuf:"varint,6,opt,name=direct,proto3" json:"direct,omitempty"`
|
||||||
LocalIceCandidateType string `protobuf:"bytes,7,opt,name=localIceCandidateType,proto3" json:"localIceCandidateType,omitempty"`
|
LocalIceCandidateType string `protobuf:"bytes,7,opt,name=localIceCandidateType,proto3" json:"localIceCandidateType,omitempty"`
|
||||||
RemoteIceCandidateType string `protobuf:"bytes,8,opt,name=remoteIceCandidateType,proto3" json:"remoteIceCandidateType,omitempty"`
|
RemoteIceCandidateType string `protobuf:"bytes,8,opt,name=remoteIceCandidateType,proto3" json:"remoteIceCandidateType,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PeerState) Reset() {
|
func (x *PeerState) Reset() {
|
||||||
@@ -699,7 +708,7 @@ func (x *PeerState) GetConnStatus() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PeerState) GetConnStatusUpdate() *timestamppb.Timestamp {
|
func (x *PeerState) GetConnStatusUpdate() *timestamp.Timestamp {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.ConnStatusUpdate
|
return x.ConnStatusUpdate
|
||||||
}
|
}
|
||||||
@@ -1021,102 +1030,104 @@ var file_daemon_proto_rawDesc = []byte{
|
|||||||
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67,
|
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67,
|
||||||
0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50,
|
||||||
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x5c, 0x0a, 0x0e, 0x53, 0x74, 0x61,
|
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x0e, 0x53, 0x74,
|
||||||
0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73,
|
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06,
|
||||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61,
|
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74,
|
||||||
0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75,
|
0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
||||||
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
||||||
0x2e, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c,
|
0x6e, 0x2e, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x66, 0x75,
|
||||||
0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52,
|
0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x61, 0x65, 0x6d,
|
||||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65,
|
0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e,
|
0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d,
|
||||||
0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x47,
|
0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a,
|
||||||
0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a,
|
||||||
0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72,
|
0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
|
||||||
0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66,
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
|
||||||
0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c,
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a,
|
||||||
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65,
|
0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79,
|
0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a,
|
||||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65,
|
0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||||
0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
|
0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68,
|
||||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
|
0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70,
|
||||||
0x22, 0xbb, 0x02, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e,
|
0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61,
|
||||||
0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16,
|
0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61,
|
||||||
0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x22, 0xbb, 0x02, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72,
|
||||||
0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74,
|
0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e,
|
0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18,
|
||||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74,
|
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a,
|
||||||
0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
|
0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||||
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x46, 0x0a,
|
||||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x6f,
|
0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||||
0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x18,
|
0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||||
0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
|
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||||
0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65,
|
0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55,
|
||||||
0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
|
0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64,
|
||||||
0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64,
|
0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12,
|
||||||
0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61,
|
0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
|
||||||
0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65,
|
|
||||||
0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65,
|
0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65,
|
||||||
0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63,
|
0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65,
|
||||||
0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x62,
|
0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a,
|
||||||
0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65,
|
0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64,
|
||||||
0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50,
|
0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x72,
|
||||||
0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74,
|
||||||
0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x72, 0x6e,
|
0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x62, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65,
|
||||||
0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20,
|
||||||
0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61,
|
0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65,
|
||||||
0x63, 0x65, 0x22, 0x3d, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12,
|
||||||
0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
|
0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61,
|
||||||
0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64,
|
0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65,
|
0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x22, 0x3d, 0x0a, 0x0b, 0x53, 0x69, 0x67,
|
||||||
0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53,
|
0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18,
|
||||||
0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f,
|
||||||
0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
|
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63,
|
||||||
0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
|
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61, 0x6e, 0x61,
|
||||||
0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01, 0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61,
|
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55,
|
||||||
0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a,
|
||||||
0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64,
|
0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
|
||||||
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01, 0x0a, 0x0a,
|
||||||
0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61,
|
||||||
0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c,
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20,
|
||||||
0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61,
|
0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e,
|
||||||
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65,
|
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61,
|
||||||
0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a,
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a,
|
||||||
0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18,
|
0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c,
|
0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e,
|
||||||
0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c,
|
0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53,
|
||||||
0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a,
|
0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65,
|
||||||
0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64,
|
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64,
|
||||||
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52,
|
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53,
|
||||||
0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32, 0xf7, 0x02, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f,
|
0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53,
|
||||||
0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69,
|
0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20,
|
||||||
0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65,
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32, 0xf7, 0x02,
|
||||||
0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
|
||||||
0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
||||||
0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53,
|
0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15,
|
||||||
0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e,
|
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73,
|
||||||
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53,
|
||||||
0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a,
|
0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
||||||
0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52,
|
0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71,
|
||||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61,
|
||||||
0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06,
|
0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65,
|
||||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e,
|
0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e,
|
||||||
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73,
|
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12,
|
0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e,
|
||||||
0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71,
|
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71,
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f,
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74,
|
||||||
0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09,
|
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33,
|
||||||
0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75,
|
0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61,
|
||||||
0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74,
|
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e,
|
||||||
0x6f, 0x33,
|
0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65,
|
||||||
|
0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||||
|
0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -1133,24 +1144,24 @@ func file_daemon_proto_rawDescGZIP() []byte {
|
|||||||
|
|
||||||
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
|
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
|
||||||
var file_daemon_proto_goTypes = []interface{}{
|
var file_daemon_proto_goTypes = []interface{}{
|
||||||
(*LoginRequest)(nil), // 0: daemon.LoginRequest
|
(*LoginRequest)(nil), // 0: daemon.LoginRequest
|
||||||
(*LoginResponse)(nil), // 1: daemon.LoginResponse
|
(*LoginResponse)(nil), // 1: daemon.LoginResponse
|
||||||
(*WaitSSOLoginRequest)(nil), // 2: daemon.WaitSSOLoginRequest
|
(*WaitSSOLoginRequest)(nil), // 2: daemon.WaitSSOLoginRequest
|
||||||
(*WaitSSOLoginResponse)(nil), // 3: daemon.WaitSSOLoginResponse
|
(*WaitSSOLoginResponse)(nil), // 3: daemon.WaitSSOLoginResponse
|
||||||
(*UpRequest)(nil), // 4: daemon.UpRequest
|
(*UpRequest)(nil), // 4: daemon.UpRequest
|
||||||
(*UpResponse)(nil), // 5: daemon.UpResponse
|
(*UpResponse)(nil), // 5: daemon.UpResponse
|
||||||
(*StatusRequest)(nil), // 6: daemon.StatusRequest
|
(*StatusRequest)(nil), // 6: daemon.StatusRequest
|
||||||
(*StatusResponse)(nil), // 7: daemon.StatusResponse
|
(*StatusResponse)(nil), // 7: daemon.StatusResponse
|
||||||
(*DownRequest)(nil), // 8: daemon.DownRequest
|
(*DownRequest)(nil), // 8: daemon.DownRequest
|
||||||
(*DownResponse)(nil), // 9: daemon.DownResponse
|
(*DownResponse)(nil), // 9: daemon.DownResponse
|
||||||
(*GetConfigRequest)(nil), // 10: daemon.GetConfigRequest
|
(*GetConfigRequest)(nil), // 10: daemon.GetConfigRequest
|
||||||
(*GetConfigResponse)(nil), // 11: daemon.GetConfigResponse
|
(*GetConfigResponse)(nil), // 11: daemon.GetConfigResponse
|
||||||
(*PeerState)(nil), // 12: daemon.PeerState
|
(*PeerState)(nil), // 12: daemon.PeerState
|
||||||
(*LocalPeerState)(nil), // 13: daemon.LocalPeerState
|
(*LocalPeerState)(nil), // 13: daemon.LocalPeerState
|
||||||
(*SignalState)(nil), // 14: daemon.SignalState
|
(*SignalState)(nil), // 14: daemon.SignalState
|
||||||
(*ManagementState)(nil), // 15: daemon.ManagementState
|
(*ManagementState)(nil), // 15: daemon.ManagementState
|
||||||
(*FullStatus)(nil), // 16: daemon.FullStatus
|
(*FullStatus)(nil), // 16: daemon.FullStatus
|
||||||
(*timestamppb.Timestamp)(nil), // 17: google.protobuf.Timestamp
|
(*timestamp.Timestamp)(nil), // 17: google.protobuf.Timestamp
|
||||||
}
|
}
|
||||||
var file_daemon_proto_depIdxs = []int32{
|
var file_daemon_proto_depIdxs = []int32{
|
||||||
16, // 0: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus
|
16, // 0: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ message StatusResponse{
|
|||||||
// status of the server.
|
// status of the server.
|
||||||
string status = 1;
|
string status = 1;
|
||||||
FullStatus fullStatus = 2;
|
FullStatus fullStatus = 2;
|
||||||
|
// NetBird daemon version
|
||||||
|
string daemonVersion = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DownRequest {}
|
message DownRequest {}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
nbStatus "github.com/netbirdio/netbird/client/status"
|
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||||
|
"github.com/netbirdio/netbird/client/system"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -92,10 +93,13 @@ func (s *Server) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if configuration exists, we just start connections.
|
// if configuration exists, we just start connections.
|
||||||
|
config, _ = internal.UpdateOldManagementPort(ctx, config, s.configPath)
|
||||||
|
|
||||||
s.config = config
|
s.config = config
|
||||||
|
|
||||||
s.statusRecorder = nbStatus.NewRecorder()
|
if s.statusRecorder == nil {
|
||||||
|
s.statusRecorder = nbStatus.NewRecorder()
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := internal.RunClient(ctx, config, s.statusRecorder); err != nil {
|
if err := internal.RunClient(ctx, config, s.statusRecorder); err != nil {
|
||||||
@@ -166,6 +170,12 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if msg.ManagementUrl == "" {
|
||||||
|
config, _ = internal.UpdateOldManagementPort(ctx, config, s.configPath)
|
||||||
|
s.config = config
|
||||||
|
s.managementURL = config.ManagementURL.String()
|
||||||
|
}
|
||||||
|
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
s.config = config
|
s.config = config
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
@@ -198,7 +208,8 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
|
|||||||
hostedClient := internal.NewHostedDeviceFlow(
|
hostedClient := internal.NewHostedDeviceFlow(
|
||||||
providerConfig.ProviderConfig.Audience,
|
providerConfig.ProviderConfig.Audience,
|
||||||
providerConfig.ProviderConfig.ClientID,
|
providerConfig.ProviderConfig.ClientID,
|
||||||
providerConfig.ProviderConfig.Domain,
|
providerConfig.ProviderConfig.TokenEndpoint,
|
||||||
|
providerConfig.ProviderConfig.DeviceAuthEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
if s.oauthAuthFlow.client != nil && s.oauthAuthFlow.client.GetClientID(ctx) == hostedClient.GetClientID(context.TODO()) {
|
if s.oauthAuthFlow.client != nil && s.oauthAuthFlow.client.GetClientID(ctx) == hostedClient.GetClientID(context.TODO()) {
|
||||||
@@ -398,7 +409,11 @@ func (s *Server) Status(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
statusResponse := proto.StatusResponse{Status: string(status)}
|
statusResponse := proto.StatusResponse{Status: string(status), DaemonVersion: system.NetbirdVersion()}
|
||||||
|
|
||||||
|
if s.statusRecorder == nil {
|
||||||
|
s.statusRecorder = nbStatus.NewRecorder()
|
||||||
|
}
|
||||||
|
|
||||||
if msg.GetFullPeerStatus {
|
if msg.GetFullPeerStatus {
|
||||||
fullStatus := s.statusRecorder.GetFullStatus()
|
fullStatus := s.statusRecorder.GetFullStatus()
|
||||||
|
|||||||
10
client/ssh/lookup.go
Normal file
10
client/ssh/lookup.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//go:build !darwin
|
||||||
|
// +build !darwin
|
||||||
|
|
||||||
|
package ssh
|
||||||
|
|
||||||
|
import "os/user"
|
||||||
|
|
||||||
|
func userNameLookup(username string) (*user.User, error) {
|
||||||
|
return user.Lookup(username)
|
||||||
|
}
|
||||||
47
client/ssh/lookup_darwin.go
Normal file
47
client/ssh/lookup_darwin.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
//go:build darwin
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func userNameLookup(username string) (*user.User, error) {
|
||||||
|
var userObject *user.User
|
||||||
|
userObject, err := user.Lookup(username)
|
||||||
|
if err != nil && err.Error() == user.UnknownUserError(username).Error() {
|
||||||
|
return idUserNameLookup(username)
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return userObject, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func idUserNameLookup(username string) (*user.User, error) {
|
||||||
|
cmd := exec.Command("id", "-P", username)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error while retrieving user with id -P command, error: %v", err)
|
||||||
|
}
|
||||||
|
colon := ":"
|
||||||
|
|
||||||
|
if !bytes.Contains(out, []byte(username+colon)) {
|
||||||
|
return nil, fmt.Errorf("unable to find user in returned string")
|
||||||
|
}
|
||||||
|
// netbird:********:501:20::0:0:netbird:/Users/netbird:/bin/zsh
|
||||||
|
parts := strings.SplitN(string(out), colon, 10)
|
||||||
|
userObject := &user.User{
|
||||||
|
Username: parts[0],
|
||||||
|
Uid: parts[2],
|
||||||
|
Gid: parts[3],
|
||||||
|
Name: parts[7],
|
||||||
|
HomeDir: parts[8],
|
||||||
|
}
|
||||||
|
return userObject, nil
|
||||||
|
}
|
||||||
@@ -137,7 +137,7 @@ func (srv *DefaultServer) sessionHandler(session ssh.Session) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
localUser, err := user.Lookup(session.User())
|
localUser, err := userNameLookup(session.User())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, err = fmt.Fprintf(session, "remote SSH server couldn't find local user %s\n", session.User()) //nolint
|
_, err = fmt.Fprintf(session, "remote SSH server couldn't find local user %s\n", session.User()) //nolint
|
||||||
err = session.Exit(1)
|
err = session.Exit(1)
|
||||||
|
|||||||
@@ -47,17 +47,19 @@ type FullStatus struct {
|
|||||||
|
|
||||||
// Status holds a state of peers, signal and management connections
|
// Status holds a state of peers, signal and management connections
|
||||||
type Status struct {
|
type Status struct {
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
peers map[string]PeerState
|
peers map[string]PeerState
|
||||||
signal SignalState
|
changeNotify map[string]chan struct{}
|
||||||
management ManagementState
|
signal SignalState
|
||||||
localPeer LocalPeerState
|
management ManagementState
|
||||||
|
localPeer LocalPeerState
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRecorder returns a new Status instance
|
// NewRecorder returns a new Status instance
|
||||||
func NewRecorder() *Status {
|
func NewRecorder() *Status {
|
||||||
return &Status{
|
return &Status{
|
||||||
peers: make(map[string]PeerState),
|
peers: make(map[string]PeerState),
|
||||||
|
changeNotify: make(map[string]chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +76,18 @@ func (d *Status) AddPeer(peerPubKey string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPeer adds peer to Daemon status map
|
||||||
|
func (d *Status) GetPeer(peerPubKey string) (PeerState, error) {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
|
state, ok := d.peers[peerPubKey]
|
||||||
|
if !ok {
|
||||||
|
return PeerState{}, errors.New("peer not found")
|
||||||
|
}
|
||||||
|
return state, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RemovePeer removes peer from Daemon status map
|
// RemovePeer removes peer from Daemon status map
|
||||||
func (d *Status) RemovePeer(peerPubKey string) error {
|
func (d *Status) RemovePeer(peerPubKey string) error {
|
||||||
d.mux.Lock()
|
d.mux.Lock()
|
||||||
@@ -113,9 +127,27 @@ func (d *Status) UpdatePeerState(receivedState PeerState) error {
|
|||||||
|
|
||||||
d.peers[receivedState.PubKey] = peerState
|
d.peers[receivedState.PubKey] = peerState
|
||||||
|
|
||||||
|
ch, found := d.changeNotify[receivedState.PubKey]
|
||||||
|
if found && ch != nil {
|
||||||
|
close(ch)
|
||||||
|
d.changeNotify[receivedState.PubKey] = nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPeerStateChangeNotifier returns a change notifier channel for a peer
|
||||||
|
func (d *Status) GetPeerStateChangeNotifier(peer string) <-chan struct{} {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
ch, found := d.changeNotify[peer]
|
||||||
|
if !found || ch == nil {
|
||||||
|
ch = make(chan struct{})
|
||||||
|
d.changeNotify[peer] = ch
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateLocalPeerState updates local peer status
|
// UpdateLocalPeerState updates local peer status
|
||||||
func (d *Status) UpdateLocalPeerState(localPeerState LocalPeerState) {
|
func (d *Status) UpdateLocalPeerState(localPeerState LocalPeerState) {
|
||||||
d.mux.Lock()
|
d.mux.Lock()
|
||||||
|
|||||||
@@ -19,6 +19,21 @@ func TestAddPeer(t *testing.T) {
|
|||||||
assert.Error(t, err, "should return error on duplicate")
|
assert.Error(t, err, "should return error on duplicate")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetPeer(t *testing.T) {
|
||||||
|
key := "abc"
|
||||||
|
status := NewRecorder()
|
||||||
|
err := status.AddPeer(key)
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
peerStatus, err := status.GetPeer(key)
|
||||||
|
assert.NoError(t, err, "shouldn't return error on getting peer")
|
||||||
|
|
||||||
|
assert.Equal(t, key, peerStatus.PubKey, "retrieved public key should match")
|
||||||
|
|
||||||
|
_, err = status.GetPeer("non_existing_key")
|
||||||
|
assert.Error(t, err, "should return error when peer doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
func TestUpdatePeerState(t *testing.T) {
|
func TestUpdatePeerState(t *testing.T) {
|
||||||
key := "abc"
|
key := "abc"
|
||||||
ip := "10.10.10.10"
|
ip := "10.10.10.10"
|
||||||
@@ -39,6 +54,31 @@ func TestUpdatePeerState(t *testing.T) {
|
|||||||
assert.Equal(t, ip, state.IP, "ip should be equal")
|
assert.Equal(t, ip, state.IP, "ip should be equal")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetPeerStateChangeNotifierLogic(t *testing.T) {
|
||||||
|
key := "abc"
|
||||||
|
ip := "10.10.10.10"
|
||||||
|
status := NewRecorder()
|
||||||
|
peerState := PeerState{
|
||||||
|
PubKey: key,
|
||||||
|
}
|
||||||
|
|
||||||
|
status.peers[key] = peerState
|
||||||
|
|
||||||
|
ch := status.GetPeerStateChangeNotifier(key)
|
||||||
|
assert.NotNil(t, ch, "channel shouldn't be nil")
|
||||||
|
|
||||||
|
peerState.IP = ip
|
||||||
|
|
||||||
|
err := status.UpdatePeerState(peerState)
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
default:
|
||||||
|
t.Errorf("channel wasn't closed after update")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRemovePeer(t *testing.T) {
|
func TestRemovePeer(t *testing.T) {
|
||||||
key := "abc"
|
key := "abc"
|
||||||
status := NewRecorder()
|
status := NewRecorder()
|
||||||
|
|||||||
@@ -4,41 +4,25 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetInfo retrieves and parses the system information
|
// GetInfo retrieves and parses the system information
|
||||||
func GetInfo(ctx context.Context) *Info {
|
func GetInfo(ctx context.Context) *Info {
|
||||||
out := _getInfo()
|
utsname := unix.Utsname{}
|
||||||
for strings.Contains(out, "broken pipe") {
|
err := unix.Uname(&utsname)
|
||||||
out = _getInfo()
|
if err != nil {
|
||||||
time.Sleep(500 * time.Millisecond)
|
fmt.Println("getInfo:", err)
|
||||||
}
|
}
|
||||||
osStr := strings.Replace(out, "\n", "", -1)
|
sysName := string(bytes.Split(utsname.Sysname[:], []byte{0})[0])
|
||||||
osStr = strings.Replace(osStr, "\r\n", "", -1)
|
machine := string(bytes.Split(utsname.Machine[:], []byte{0})[0])
|
||||||
osInfo := strings.Split(osStr, " ")
|
release := string(bytes.Split(utsname.Release[:], []byte{0})[0])
|
||||||
gio := &Info{Kernel: osInfo[0], OSVersion: osInfo[1], Core: osInfo[1], Platform: osInfo[2], OS: osInfo[0], GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
gio := &Info{Kernel: sysName, OSVersion: release, Core: release, Platform: machine, OS: sysName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
||||||
gio.Hostname, _ = os.Hostname()
|
gio.Hostname, _ = os.Hostname()
|
||||||
gio.WiretrusteeVersion = NetbirdVersion()
|
gio.WiretrusteeVersion = NetbirdVersion()
|
||||||
gio.UIVersion = extractUserAgent(ctx)
|
gio.UIVersion = extractUserAgent(ctx)
|
||||||
|
|
||||||
return gio
|
return gio
|
||||||
}
|
}
|
||||||
|
|
||||||
func _getInfo() string {
|
|
||||||
cmd := exec.Command("uname", "-srm")
|
|
||||||
cmd.Stdin = strings.NewReader("some input")
|
|
||||||
var out bytes.Buffer
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("getInfo:", err)
|
|
||||||
}
|
|
||||||
return out.String()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,36 +1,17 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetInfo retrieves and parses the system information
|
// GetInfo retrieves and parses the system information
|
||||||
func GetInfo(ctx context.Context) *Info {
|
func GetInfo(ctx context.Context) *Info {
|
||||||
cmd := exec.Command("cmd", "ver")
|
ver := getOSVersion()
|
||||||
cmd.Stdin = strings.NewReader("some")
|
|
||||||
var out bytes.Buffer
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
osStr := strings.Replace(out.String(), "\n", "", -1)
|
|
||||||
osStr = strings.Replace(osStr, "\r\n", "", -1)
|
|
||||||
tmp1 := strings.Index(osStr, "[Version")
|
|
||||||
tmp2 := strings.Index(osStr, "]")
|
|
||||||
var ver string
|
|
||||||
if tmp1 == -1 || tmp2 == -1 {
|
|
||||||
ver = "unknown"
|
|
||||||
} else {
|
|
||||||
ver = osStr[tmp1+9 : tmp2]
|
|
||||||
}
|
|
||||||
gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
||||||
gio.Hostname, _ = os.Hostname()
|
gio.Hostname, _ = os.Hostname()
|
||||||
gio.WiretrusteeVersion = NetbirdVersion()
|
gio.WiretrusteeVersion = NetbirdVersion()
|
||||||
@@ -38,3 +19,37 @@ func GetInfo(ctx context.Context) *Info {
|
|||||||
|
|
||||||
return gio
|
return gio
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOSVersion() string {
|
||||||
|
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return "0.0.0.0"
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
deferErr := k.Close()
|
||||||
|
if deferErr != nil {
|
||||||
|
log.Error(deferErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
major, _, err := k.GetIntegerValue("CurrentMajorVersionNumber")
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
minor, _, err := k.GetIntegerValue("CurrentMinorVersionNumber")
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
build, _, err := k.GetStringValue("CurrentBuildNumber")
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
// Update Build Revision
|
||||||
|
ubr, _, err := k.GetIntegerValue("UBR")
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
ver := fmt.Sprintf("%d.%d.%s.%d", major, minor, build, ubr)
|
||||||
|
return ver
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@@ -325,12 +324,12 @@ func (s *serviceClient) updateStatus() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.Status == string(internal.StatusConnected) {
|
if status.Status == string(internal.StatusConnected) && !s.mUp.Disabled() {
|
||||||
systray.SetIcon(s.icConnected)
|
systray.SetIcon(s.icConnected)
|
||||||
s.mStatus.SetTitle("Connected")
|
s.mStatus.SetTitle("Connected")
|
||||||
s.mUp.Disable()
|
s.mUp.Disable()
|
||||||
s.mDown.Enable()
|
s.mDown.Enable()
|
||||||
} else {
|
} else if status.Status != string(internal.StatusConnected) && s.mUp.Disabled() {
|
||||||
systray.SetIcon(s.icDisconnected)
|
systray.SetIcon(s.icDisconnected)
|
||||||
s.mStatus.SetTitle("Disconnected")
|
s.mStatus.SetTitle("Disconnected")
|
||||||
s.mDown.Disable()
|
s.mDown.Disable()
|
||||||
@@ -501,7 +500,7 @@ func (s *serviceClient) getSrvConfig() {
|
|||||||
// checkPIDFile exists and return error, or write new.
|
// checkPIDFile exists and return error, or write new.
|
||||||
func checkPIDFile() error {
|
func checkPIDFile() error {
|
||||||
pidFile := path.Join(os.TempDir(), "wiretrustee-ui.pid")
|
pidFile := path.Join(os.TempDir(), "wiretrustee-ui.pid")
|
||||||
if piddata, err := ioutil.ReadFile(pidFile); err == nil {
|
if piddata, err := os.ReadFile(pidFile); err == nil {
|
||||||
if pid, err := strconv.Atoi(string(piddata)); err == nil {
|
if pid, err := strconv.Atoi(string(piddata)); err == nil {
|
||||||
if process, err := os.FindProcess(pid); err == nil {
|
if process, err := os.FindProcess(pid); err == nil {
|
||||||
if err := process.Signal(syscall.Signal(0)); err == nil {
|
if err := process.Signal(syscall.Signal(0)); err == nil {
|
||||||
@@ -511,5 +510,5 @@ func checkPIDFile() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(pidFile, []byte(fmt.Sprintf("%d", os.Getpid())), 0o664)
|
return os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", os.Getpid())), 0o664)
|
||||||
}
|
}
|
||||||
|
|||||||
6
dns/dns.go
Normal file
6
dns/dns.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Package dns implement dns types and standard methods and functions
|
||||||
|
// to parse and normalize dns records and configuration
|
||||||
|
package dns
|
||||||
|
|
||||||
|
// DefaultDNSPort well-known port number
|
||||||
|
const DefaultDNSPort = 53
|
||||||
184
dns/nameserver.go
Normal file
184
dns/nameserver.go
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxGroupNameChar maximum group name size
|
||||||
|
MaxGroupNameChar = 40
|
||||||
|
// InvalidNameServerType invalid nameserver type
|
||||||
|
InvalidNameServerType NameServerType = iota
|
||||||
|
// UDPNameServerType udp nameserver type
|
||||||
|
UDPNameServerType
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// InvalidNameServerTypeString invalid nameserver type as string
|
||||||
|
InvalidNameServerTypeString = "invalid"
|
||||||
|
// UDPNameServerTypeString udp nameserver type as string
|
||||||
|
UDPNameServerTypeString = "udp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NameServerType nameserver type
|
||||||
|
type NameServerType int
|
||||||
|
|
||||||
|
// String returns nameserver type string
|
||||||
|
func (n NameServerType) String() string {
|
||||||
|
switch n {
|
||||||
|
case UDPNameServerType:
|
||||||
|
return UDPNameServerTypeString
|
||||||
|
default:
|
||||||
|
return InvalidNameServerTypeString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToNameServerType returns a nameserver type
|
||||||
|
func ToNameServerType(typeString string) NameServerType {
|
||||||
|
switch typeString {
|
||||||
|
case UDPNameServerTypeString:
|
||||||
|
return UDPNameServerType
|
||||||
|
default:
|
||||||
|
return InvalidNameServerType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameServerGroup group of nameservers and with group ids
|
||||||
|
type NameServerGroup struct {
|
||||||
|
// ID identifier of group
|
||||||
|
ID string
|
||||||
|
// Name group name
|
||||||
|
Name string
|
||||||
|
// Description group description
|
||||||
|
Description string
|
||||||
|
// NameServers list of nameservers
|
||||||
|
NameServers []NameServer
|
||||||
|
// Groups list of peer group IDs to distribute the nameservers information
|
||||||
|
Groups []string
|
||||||
|
// Enabled group status
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameServer represents a DNS nameserver
|
||||||
|
type NameServer struct {
|
||||||
|
// IP address of nameserver
|
||||||
|
IP netip.Addr
|
||||||
|
// NSType nameserver type
|
||||||
|
NSType NameServerType
|
||||||
|
// Port nameserver listening port
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy copies a nameserver object
|
||||||
|
func (n *NameServer) Copy() *NameServer {
|
||||||
|
return &NameServer{
|
||||||
|
IP: n.IP,
|
||||||
|
NSType: n.NSType,
|
||||||
|
Port: n.Port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEqual compares one nameserver with the other
|
||||||
|
func (n *NameServer) IsEqual(other *NameServer) bool {
|
||||||
|
return other.IP == n.IP &&
|
||||||
|
other.NSType == n.NSType &&
|
||||||
|
other.Port == n.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseNameServerURL parses a nameserver url in the format <type>://<ip>:<port>, e.g., udp://1.1.1.1:53
|
||||||
|
func ParseNameServerURL(nsURL string) (NameServer, error) {
|
||||||
|
parsedURL, err := url.Parse(nsURL)
|
||||||
|
if err != nil {
|
||||||
|
return NameServer{}, err
|
||||||
|
}
|
||||||
|
var ns NameServer
|
||||||
|
parsedScheme := strings.ToLower(parsedURL.Scheme)
|
||||||
|
nsType := ToNameServerType(parsedScheme)
|
||||||
|
if nsType == InvalidNameServerType {
|
||||||
|
return NameServer{}, fmt.Errorf("invalid nameserver url schema type, got %s", parsedScheme)
|
||||||
|
}
|
||||||
|
ns.NSType = nsType
|
||||||
|
|
||||||
|
parsedPort, err := strconv.Atoi(parsedURL.Port())
|
||||||
|
if err != nil {
|
||||||
|
return NameServer{}, fmt.Errorf("invalid nameserver url port, got %s", parsedURL.Port())
|
||||||
|
}
|
||||||
|
ns.Port = parsedPort
|
||||||
|
|
||||||
|
parsedAddr, err := netip.ParseAddr(parsedURL.Hostname())
|
||||||
|
if err != nil {
|
||||||
|
return NameServer{}, fmt.Errorf("invalid nameserver url IP, got %s", parsedURL.Hostname())
|
||||||
|
}
|
||||||
|
|
||||||
|
ns.IP = parsedAddr
|
||||||
|
|
||||||
|
return ns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy copies a nameserver group object
|
||||||
|
func (g *NameServerGroup) Copy() *NameServerGroup {
|
||||||
|
return &NameServerGroup{
|
||||||
|
ID: g.ID,
|
||||||
|
Name: g.Name,
|
||||||
|
Description: g.Description,
|
||||||
|
NameServers: g.NameServers,
|
||||||
|
Groups: g.Groups,
|
||||||
|
Enabled: g.Enabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEqual compares one nameserver group with the other
|
||||||
|
func (g *NameServerGroup) IsEqual(other *NameServerGroup) bool {
|
||||||
|
return other.ID == g.ID &&
|
||||||
|
other.Name == g.Name &&
|
||||||
|
other.Description == g.Description &&
|
||||||
|
compareNameServerList(g.NameServers, other.NameServers) &&
|
||||||
|
compareGroupsList(g.Groups, other.Groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareNameServerList(list, other []NameServer) bool {
|
||||||
|
if len(list) != len(other) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ns := range list {
|
||||||
|
if !containsNameServer(ns, other) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsNameServer(element NameServer, list []NameServer) bool {
|
||||||
|
for _, ns := range list {
|
||||||
|
if ns.IsEqual(&element) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareGroupsList(list, other []string) bool {
|
||||||
|
if len(list) != len(other) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, id := range list {
|
||||||
|
match := false
|
||||||
|
for _, otherID := range other {
|
||||||
|
if id == otherID {
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
BIN
docs/media/netbird-sso-mfa-demo.gif
Normal file
BIN
docs/media/netbird-sso-mfa-demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 887 KiB |
@@ -8,17 +8,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// CreateCertManager wraps common logic of generating Let's encrypt certificate.
|
// CreateCertManager wraps common logic of generating Let's encrypt certificate.
|
||||||
func CreateCertManager(datadir string, letsencryptDomain string) *autocert.Manager {
|
func CreateCertManager(datadir string, letsencryptDomain string) (*autocert.Manager, error) {
|
||||||
certDir := filepath.Join(datadir, "letsencrypt")
|
certDir := filepath.Join(datadir, "letsencrypt")
|
||||||
|
|
||||||
if _, err := os.Stat(certDir); os.IsNotExist(err) {
|
if _, err := os.Stat(certDir); os.IsNotExist(err) {
|
||||||
err = os.MkdirAll(certDir, os.ModeDir)
|
err = os.MkdirAll(certDir, os.ModeDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed creating Let's encrypt certdir: %s: %v", certDir, err)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("running with Let's encrypt with domain %s. Cert will be stored in %s", letsencryptDomain, certDir)
|
log.Infof("running with LetsEncrypt (%s). Cert will be stored in %s", letsencryptDomain, certDir)
|
||||||
|
|
||||||
certManager := &autocert.Manager{
|
certManager := &autocert.Manager{
|
||||||
Prompt: autocert.AcceptTOS,
|
Prompt: autocert.AcceptTOS,
|
||||||
@@ -26,5 +26,5 @@ func CreateCertManager(datadir string, letsencryptDomain string) *autocert.Manag
|
|||||||
HostPolicy: autocert.HostWhitelist(letsencryptDomain),
|
HostPolicy: autocert.HostWhitelist(letsencryptDomain),
|
||||||
}
|
}
|
||||||
|
|
||||||
return certManager
|
return certManager, nil
|
||||||
}
|
}
|
||||||
|
|||||||
25
go.mod
25
go.mod
@@ -11,7 +11,7 @@ require (
|
|||||||
github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7 //keep this version otherwise wiretrustee up command breaks
|
github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7 //keep this version otherwise wiretrustee up command breaks
|
||||||
github.com/onsi/ginkgo v1.16.5
|
github.com/onsi/ginkgo v1.16.5
|
||||||
github.com/onsi/gomega v1.18.1
|
github.com/onsi/gomega v1.18.1
|
||||||
github.com/pion/ice/v2 v2.1.17
|
github.com/pion/ice/v2 v2.2.7
|
||||||
github.com/rs/cors v1.8.0
|
github.com/rs/cors v1.8.0
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/spf13/cobra v1.3.0
|
github.com/spf13/cobra v1.3.0
|
||||||
@@ -30,15 +30,19 @@ require (
|
|||||||
require (
|
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/coreos/go-iptables v0.6.0
|
||||||
github.com/creack/pty v1.1.18
|
github.com/creack/pty v1.1.18
|
||||||
github.com/eko/gocache/v2 v2.3.1
|
github.com/eko/gocache/v3 v3.1.1
|
||||||
github.com/getlantern/systray v1.2.1
|
github.com/getlantern/systray v1.2.1
|
||||||
github.com/gliderlabs/ssh v0.3.4
|
github.com/gliderlabs/ssh v0.3.4
|
||||||
|
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
|
||||||
|
github.com/libp2p/go-netroute v0.2.0
|
||||||
github.com/magiconair/properties v1.8.5
|
github.com/magiconair/properties v1.8.5
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/rs/xid v1.3.0
|
github.com/rs/xid v1.3.0
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/stretchr/testify v1.8.0
|
||||||
|
golang.org/x/net v0.0.0-20220630215102-69896b714898
|
||||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
|
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,6 +70,7 @@ require (
|
|||||||
github.com/godbus/dbus/v5 v5.0.4 // indirect
|
github.com/godbus/dbus/v5 v5.0.4 // indirect
|
||||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
|
||||||
github.com/google/go-cmp v0.5.7 // indirect
|
github.com/google/go-cmp v0.5.7 // indirect
|
||||||
|
github.com/google/gopacket v1.1.19 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
@@ -75,13 +80,13 @@ require (
|
|||||||
github.com/nxadm/tail v1.4.8 // indirect
|
github.com/nxadm/tail v1.4.8 // indirect
|
||||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||||
github.com/pegasus-kv/thrift v0.13.0 // indirect
|
github.com/pegasus-kv/thrift v0.13.0 // indirect
|
||||||
github.com/pion/dtls/v2 v2.1.2 // indirect
|
github.com/pion/dtls/v2 v2.1.5 // indirect
|
||||||
github.com/pion/logging v0.2.2 // indirect
|
github.com/pion/logging v0.2.2 // indirect
|
||||||
github.com/pion/mdns v0.0.5 // indirect
|
github.com/pion/mdns v0.0.5 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pion/stun v0.3.5 // indirect
|
github.com/pion/stun v0.3.5 // indirect
|
||||||
github.com/pion/transport v0.13.0 // indirect
|
github.com/pion/transport v0.13.1 // indirect
|
||||||
github.com/pion/turn/v2 v2.0.7 // indirect
|
github.com/pion/turn/v2 v2.0.8 // indirect
|
||||||
github.com/pion/udp v0.1.1 // indirect
|
github.com/pion/udp v0.1.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.12.2 // indirect
|
github.com/prometheus/client_golang v1.12.2 // indirect
|
||||||
@@ -94,9 +99,9 @@ require (
|
|||||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
|
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
||||||
github.com/yuin/goldmark v1.4.1 // indirect
|
github.com/yuin/goldmark v1.4.1 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
|
||||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 // indirect
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||||
golang.org/x/net v0.0.0-20220513224357-95641704303c // indirect
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
|
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
|
||||||
golang.org/x/tools v0.1.10 // indirect
|
golang.org/x/tools v0.1.10 // indirect
|
||||||
@@ -108,9 +113,11 @@ require (
|
|||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
honnef.co/go/tools v0.2.2 // indirect
|
honnef.co/go/tools v0.2.2 // indirect
|
||||||
k8s.io/apimachinery v0.23.5 // indirect
|
k8s.io/apimachinery v0.23.5 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/pion/ice/v2 => github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb
|
replace github.com/kardianos/service => github.com/netbirdio/service v0.0.0-20220905002524-6ac14ad5ea84
|
||||||
|
|
||||||
|
replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-20221012095658-dc8eda872c0c
|
||||||
|
|||||||
56
go.sum
56
go.sum
@@ -115,6 +115,8 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
|
|||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
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/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
|
||||||
|
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
@@ -132,8 +134,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
|
|||||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
github.com/eko/gocache/v2 v2.3.1 h1:8MMkfqGJ0KIA9OXT0rXevcEIrU16oghrGDiIDJDFCa0=
|
github.com/eko/gocache/v3 v3.1.1 h1:r3CBwLnqPkcK56h9Do2CWw1kZ4TeKK0wDE1Oo/YZnhs=
|
||||||
github.com/eko/gocache/v2 v2.3.1/go.mod h1:l2z8OmpZHL0CpuzDJtxm267eF3mZW1NqUsMj+sKrbUs=
|
github.com/eko/gocache/v3 v3.1.1/go.mod h1:UpP/LyHAioP/a/dizgl0MpgZ3A3CkS4NbG/mWkGTQ9M=
|
||||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
@@ -176,8 +178,6 @@ github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0F
|
|||||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
||||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||||
github.com/getlantern/systray v1.2.1 h1:udsC2k98v2hN359VTFShuQW6GGprRprw6kD6539JikI=
|
|
||||||
github.com/getlantern/systray v1.2.1/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
|
||||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
@@ -283,10 +283,14 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
|
|||||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||||
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||||
|
github.com/google/nftables v0.0.0-20220808154552-2eca00135732 h1:csc7dT82JiSLvq4aMyQMIQDL7986NH6Wxf/QrvOj55A=
|
||||||
|
github.com/google/nftables v0.0.0-20220808154552-2eca00135732/go.mod h1:b97ulCCFipUC+kSin+zygkvUVpx0vyIAwxXFdY3PlNc=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
@@ -383,8 +387,6 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
|
|||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7 h1:oohm9Rk9JAxxmp2NLZa7Kebgz9h4+AJDcc64txg3dQ0=
|
|
||||||
github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
|
||||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
@@ -401,6 +403,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||||
|
github.com/libp2p/go-netroute v0.2.0 h1:0FpsbsvuSnAhXFnCY0VLFbJOzaK0VnP0r1QT/o4nWRE=
|
||||||
|
github.com/libp2p/go-netroute v0.2.0/go.mod h1:Vio7LTzZ+6hoT4CMZi5/6CpY3Snzh2vgZhWgxMNwlQI=
|
||||||
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
|
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
|
||||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||||
@@ -466,6 +470,10 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
|
|||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||||
|
github.com/netbirdio/service v0.0.0-20220905002524-6ac14ad5ea84 h1:u8kpzR9ld1uAeH/BAXsS0SfcnhooLWeO7UgHSBVPD9I=
|
||||||
|
github.com/netbirdio/service v0.0.0-20220905002524-6ac14ad5ea84/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||||
|
github.com/netbirdio/systray v0.0.0-20221012095658-dc8eda872c0c h1:wK/s4nyZj/GF/kFJQjX6nqNfE0G3gcqd6hhnPCyp4sw=
|
||||||
|
github.com/netbirdio/systray v0.0.0-20221012095658-dc8eda872c0c/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
@@ -497,8 +505,10 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK
|
|||||||
github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4=
|
github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4=
|
||||||
github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ=
|
github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ=
|
||||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pion/dtls/v2 v2.1.2 h1:22Q1Jk9L++Yo7BIf9130MonNPfPVb+YgdYLeyQotuAA=
|
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
|
||||||
github.com/pion/dtls/v2 v2.1.2/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
||||||
|
github.com/pion/ice/v2 v2.2.7 h1:kG9tux3WdYUSqqqnf+O5zKlpy41PdlvLUBlYJeV2emQ=
|
||||||
|
github.com/pion/ice/v2 v2.2.7/go.mod h1:Ckj7cWZ717rtU01YoDQA9ntGWCk95D42uVZ8sI0EL+8=
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||||
@@ -508,10 +518,11 @@ github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TB
|
|||||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||||
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
|
|
||||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||||
github.com/pion/turn/v2 v2.0.7 h1:SZhc00WDovK6czaN1RSiHqbwANtIO6wfZQsU0m0KNE8=
|
github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
|
||||||
github.com/pion/turn/v2 v2.0.7/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
|
||||||
|
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||||
|
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
@@ -598,6 +609,7 @@ github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15
|
|||||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
@@ -605,8 +617,9 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
@@ -616,8 +629,6 @@ github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJ
|
|||||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||||
github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb h1:CU1/+CEeCPvYXgfAyqTJXSQSf6hW3wsWM6Dfz6HkHEQ=
|
|
||||||
github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb/go.mod h1:XT1Nrb4OxbVFPffbQMbq4PaeEkpRLVzdphh3fjrw7DY=
|
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@@ -654,7 +665,7 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
|
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
|
||||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -667,6 +678,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
|
||||||
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
||||||
@@ -748,6 +761,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||||
|
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
@@ -763,8 +777,10 @@ golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220513224357-95641704303c h1:nF9mHSvoKBLkQNQhJZNsc66z2UzAMUbLGjC95CF3pU0=
|
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
|
||||||
|
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -870,6 +886,7 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -896,6 +913,8 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU=
|
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU=
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@@ -1175,8 +1194,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@@ -9,6 +9,16 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetName returns the interface name
|
||||||
|
func (w *WGIface) GetName() string {
|
||||||
|
return w.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddress returns the interface address
|
||||||
|
func (w *WGIface) GetAddress() WGAddress {
|
||||||
|
return w.Address
|
||||||
|
}
|
||||||
|
|
||||||
// configureDevice configures the wireguard device
|
// configureDevice configures the wireguard device
|
||||||
func (w *WGIface) configureDevice(config wgtypes.Config) error {
|
func (w *WGIface) configureDevice(config wgtypes.Config) error {
|
||||||
wg, err := wgctrl.New()
|
wg, err := wgctrl.New()
|
||||||
@@ -112,6 +122,114 @@ func (w *WGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.D
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddAllowedIP adds a prefix to the allowed IPs list of peer
|
||||||
|
func (w *WGIface) AddAllowedIP(peerKey string, allowedIP string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
log.Debugf("adding allowed IP to interface %s and peer %s: allowed IP %s ", w.Name, peerKey, allowedIP)
|
||||||
|
|
||||||
|
_, ipNet, err := net.ParseCIDR(allowedIP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
peer := wgtypes.PeerConfig{
|
||||||
|
PublicKey: peerKeyParsed,
|
||||||
|
UpdateOnly: true,
|
||||||
|
ReplaceAllowedIPs: false,
|
||||||
|
AllowedIPs: []net.IPNet{*ipNet},
|
||||||
|
}
|
||||||
|
|
||||||
|
config := wgtypes.Config{
|
||||||
|
Peers: []wgtypes.PeerConfig{peer},
|
||||||
|
}
|
||||||
|
err = w.configureDevice(config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("received error \"%v\" while adding allowed Ip to peer on interface %s with settings: allowed ips %s", err, w.Name, allowedIP)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAllowedIP removes a prefix from the allowed IPs list of peer
|
||||||
|
func (w *WGIface) RemoveAllowedIP(peerKey string, allowedIP string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
log.Debugf("removing allowed IP from interface %s and peer %s: allowed IP %s ", w.Name, peerKey, allowedIP)
|
||||||
|
|
||||||
|
_, ipNet, err := net.ParseCIDR(allowedIP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
existingPeer, err := getPeer(w.Name, peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newAllowedIPs := existingPeer.AllowedIPs
|
||||||
|
|
||||||
|
for i, existingAllowedIP := range existingPeer.AllowedIPs {
|
||||||
|
if existingAllowedIP.String() == ipNet.String() {
|
||||||
|
newAllowedIPs = append(existingPeer.AllowedIPs[:i], existingPeer.AllowedIPs[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
peer := wgtypes.PeerConfig{
|
||||||
|
PublicKey: peerKeyParsed,
|
||||||
|
UpdateOnly: true,
|
||||||
|
ReplaceAllowedIPs: true,
|
||||||
|
AllowedIPs: newAllowedIPs,
|
||||||
|
}
|
||||||
|
|
||||||
|
config := wgtypes.Config{
|
||||||
|
Peers: []wgtypes.PeerConfig{peer},
|
||||||
|
}
|
||||||
|
err = w.configureDevice(config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("received error \"%v\" while removing allowed IP from peer on interface %s with settings: allowed ips %s", err, w.Name, allowedIP)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPeer(ifaceName, peerPubKey string) (wgtypes.Peer, error) {
|
||||||
|
wg, err := wgctrl.New()
|
||||||
|
if err != nil {
|
||||||
|
return wgtypes.Peer{}, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = wg.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("got error while closing wgctl: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wgDevice, err := wg.Device(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return wgtypes.Peer{}, err
|
||||||
|
}
|
||||||
|
for _, peer := range wgDevice.Peers {
|
||||||
|
if peer.PublicKey.String() == peerPubKey {
|
||||||
|
return peer, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wgtypes.Peer{}, fmt.Errorf("peer not found")
|
||||||
|
}
|
||||||
|
|
||||||
// RemovePeer removes a Wireguard Peer from the interface iface
|
// RemovePeer removes a Wireguard Peer from the interface iface
|
||||||
func (w *WGIface) RemovePeer(peerKey string) error {
|
func (w *WGIface) RemovePeer(peerKey string) error {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func (w *WGIface) assignAddr() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WireguardModExists check if we can load wireguard mod (linux only)
|
// WireguardModuleIsLoaded check if we can load wireguard mod (linux only)
|
||||||
func WireguardModExists() bool {
|
func WireguardModuleIsLoaded() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,29 @@
|
|||||||
package iface
|
package iface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NativeLink struct {
|
type NativeLink struct {
|
||||||
Link *netlink.Link
|
Link *netlink.Link
|
||||||
}
|
}
|
||||||
|
|
||||||
// WireguardModExists check if we can load wireguard mod (linux only)
|
|
||||||
func WireguardModExists() bool {
|
|
||||||
link := newWGLink("mustnotexist")
|
|
||||||
|
|
||||||
// We willingly try to create a device with an invalid
|
|
||||||
// MTU here as the validation of the MTU will be performed after
|
|
||||||
// the validation of the link kind and hence allows us to check
|
|
||||||
// for the existance of the wireguard module without actually
|
|
||||||
// creating a link.
|
|
||||||
//
|
|
||||||
// As a side-effect, this will also let the kernel lazy-load
|
|
||||||
// the wireguard module.
|
|
||||||
link.attrs.MTU = math.MaxInt
|
|
||||||
|
|
||||||
err := netlink.LinkAdd(link)
|
|
||||||
|
|
||||||
return errors.Is(err, syscall.EINVAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates a new Wireguard interface, sets a given IP and brings it up.
|
// Create creates a new Wireguard interface, sets a given IP and brings it up.
|
||||||
// Will reuse an existing one.
|
// Will reuse an existing one.
|
||||||
func (w *WGIface) Create() error {
|
func (w *WGIface) Create() error {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
if WireguardModExists() {
|
if WireguardModuleIsLoaded() {
|
||||||
log.Info("using kernel WireGuard")
|
log.Info("using kernel WireGuard")
|
||||||
return w.createWithKernel()
|
return w.createWithKernel()
|
||||||
} else {
|
} else {
|
||||||
|
if !tunModuleIsLoaded() {
|
||||||
|
return fmt.Errorf("couldn't check or load tun module")
|
||||||
|
}
|
||||||
log.Info("using userspace WireGuard")
|
log.Info("using userspace WireGuard")
|
||||||
return w.createWithUserspace()
|
return w.createWithUserspace()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ func Test_UpdatePeer(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
peer, err := getPeer(ifaceName, peerPubKey, t)
|
peer, err := getPeer(ifaceName, peerPubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -289,7 +289,7 @@ func Test_RemovePeer(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, err = getPeer(ifaceName, peerPubKey, t)
|
_, err = getPeer(ifaceName, peerPubKey)
|
||||||
if err.Error() != "peer not found" {
|
if err.Error() != "peer not found" {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -378,7 +378,7 @@ func Test_ConnectPeers(t *testing.T) {
|
|||||||
t.Fatalf("waiting for peer handshake timeout after %s", timeout.String())
|
t.Fatalf("waiting for peer handshake timeout after %s", timeout.String())
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
peer, gpErr := getPeer(peer1ifaceName, peer2Key.PublicKey().String(), t)
|
peer, gpErr := getPeer(peer1ifaceName, peer2Key.PublicKey().String())
|
||||||
if gpErr != nil {
|
if gpErr != nil {
|
||||||
t.Fatal(gpErr)
|
t.Fatal(gpErr)
|
||||||
}
|
}
|
||||||
@@ -389,28 +389,3 @@ func Test_ConnectPeers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPeer(ifaceName, peerPubKey string, t *testing.T) (wgtypes.Peer, error) {
|
|
||||||
emptyPeer := wgtypes.Peer{}
|
|
||||||
wg, err := wgctrl.New()
|
|
||||||
if err != nil {
|
|
||||||
return emptyPeer, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
err = wg.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
wgDevice, err := wg.Device(ifaceName)
|
|
||||||
if err != nil {
|
|
||||||
return emptyPeer, err
|
|
||||||
}
|
|
||||||
for _, peer := range wgDevice.Peers {
|
|
||||||
if peer.PublicKey.String() == peerPubKey {
|
|
||||||
return peer, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return emptyPeer, fmt.Errorf("peer not found")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (w *WGIface) UpdateAddr(newAddr string) error {
|
|||||||
return w.assignAddr(luid)
|
return w.assignAddr(luid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WireguardModExists check if we can load wireguard mod (linux only)
|
// WireguardModuleIsLoaded check if we can load wireguard mod (linux only)
|
||||||
func WireguardModExists() bool {
|
func WireguardModuleIsLoaded() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
349
iface/module_linux.go
Normal file
349
iface/module_linux.go
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
// Package iface provides wireguard network interface creation and management
|
||||||
|
package iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"io/fs"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Holds logic to check existence of kernel modules used by wireguard interfaces
|
||||||
|
// Copied from https://github.com/paultag/go-modprobe and
|
||||||
|
// https://github.com/pmorjan/kmod
|
||||||
|
|
||||||
|
type status int
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultModuleDir = "/lib/modules"
|
||||||
|
unknown status = iota
|
||||||
|
unloaded
|
||||||
|
unloading
|
||||||
|
loading
|
||||||
|
live
|
||||||
|
inuse
|
||||||
|
)
|
||||||
|
|
||||||
|
type module struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrModuleNotFound is the error resulting if a module can't be found.
|
||||||
|
ErrModuleNotFound = errors.New("module not found")
|
||||||
|
moduleLibDir = defaultModuleDir
|
||||||
|
// get the root directory for the kernel modules. If this line panics,
|
||||||
|
// it's because getModuleRoot has failed to get the uname of the running
|
||||||
|
// kernel (likely a non-POSIX system, but maybe a broken kernel?)
|
||||||
|
moduleRoot = getModuleRoot()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get the module root (/lib/modules/$(uname -r)/)
|
||||||
|
func getModuleRoot() string {
|
||||||
|
uname := unix.Utsname{}
|
||||||
|
if err := unix.Uname(&uname); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for ; uname.Release[i] != 0; i++ {
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(moduleLibDir, string(uname.Release[:i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// tunModuleIsLoaded check if tun module exist, if is not attempt to load it
|
||||||
|
func tunModuleIsLoaded() bool {
|
||||||
|
_, err := os.Stat("/dev/net/tun")
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("couldn't access device /dev/net/tun, go error %v, "+
|
||||||
|
"will attempt to load tun module, if running on container add flag --cap-add=NET_ADMIN", err)
|
||||||
|
|
||||||
|
tunLoaded, err := tryToLoadModule("tun")
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to find or load tun module, got error: %v", err)
|
||||||
|
}
|
||||||
|
return tunLoaded
|
||||||
|
}
|
||||||
|
|
||||||
|
// WireguardModuleIsLoaded check if we can load wireguard mod (linux only)
|
||||||
|
func WireguardModuleIsLoaded() bool {
|
||||||
|
if canCreateFakeWireguardInterface() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded, err := tryToLoadModule("wireguard")
|
||||||
|
if err != nil {
|
||||||
|
log.Info(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
func canCreateFakeWireguardInterface() bool {
|
||||||
|
link := newWGLink("mustnotexist")
|
||||||
|
|
||||||
|
// We willingly try to create a device with an invalid
|
||||||
|
// MTU here as the validation of the MTU will be performed after
|
||||||
|
// the validation of the link kind and hence allows us to check
|
||||||
|
// for the existance of the wireguard module without actually
|
||||||
|
// creating a link.
|
||||||
|
//
|
||||||
|
// As a side-effect, this will also let the kernel lazy-load
|
||||||
|
// the wireguard module.
|
||||||
|
link.attrs.MTU = math.MaxInt
|
||||||
|
|
||||||
|
err := netlink.LinkAdd(link)
|
||||||
|
|
||||||
|
return errors.Is(err, syscall.EINVAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryToLoadModule(moduleName string) (bool, error) {
|
||||||
|
if isModuleEnabled(moduleName) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
modulePath, err := getModulePath(moduleName)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("couldn't find module path for %s, error: %v", moduleName, err)
|
||||||
|
}
|
||||||
|
if modulePath == "" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("trying to load %s module", moduleName)
|
||||||
|
|
||||||
|
err = loadModuleWithDependencies(moduleName, modulePath)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("couldn't load %s module, error: %v", moduleName, err)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isModuleEnabled(name string) bool {
|
||||||
|
builtin, builtinErr := isBuiltinModule(name)
|
||||||
|
state, statusErr := moduleStatus(name)
|
||||||
|
return (builtinErr == nil && builtin) || (statusErr == nil && state >= loading)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModulePath(name string) (string, error) {
|
||||||
|
var foundPath string
|
||||||
|
skipRemainingDirs := false
|
||||||
|
|
||||||
|
err := filepath.WalkDir(
|
||||||
|
moduleRoot,
|
||||||
|
func(path string, info fs.DirEntry, err error) error {
|
||||||
|
if skipRemainingDirs {
|
||||||
|
return fs.SkipDir
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// skip broken files
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.Type().IsRegular() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nameFromPath := pathToName(path)
|
||||||
|
if nameFromPath == name {
|
||||||
|
foundPath = path
|
||||||
|
skipRemainingDirs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathToName(s string) string {
|
||||||
|
s = filepath.Base(s)
|
||||||
|
for ext := filepath.Ext(s); ext != ""; ext = filepath.Ext(s) {
|
||||||
|
s = strings.TrimSuffix(s, ext)
|
||||||
|
}
|
||||||
|
return cleanName(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanName(s string) string {
|
||||||
|
return strings.ReplaceAll(strings.TrimSpace(s), "-", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBuiltinModule(name string) (bool, error) {
|
||||||
|
f, err := os.Open(filepath.Join(moduleRoot, "/modules.builtin"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := f.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed closing modules.builtin file, %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if pathToName(line) == name {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return found, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// /proc/modules
|
||||||
|
// name | memory size | reference count | references | state: <Live|Loading|Unloading>
|
||||||
|
// macvlan 28672 1 macvtap, Live 0x0000000000000000
|
||||||
|
func moduleStatus(name string) (status, error) {
|
||||||
|
state := unknown
|
||||||
|
f, err := os.Open("/proc/modules")
|
||||||
|
if err != nil {
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := f.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed closing /proc/modules file, %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
state = unloaded
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
fields := strings.Fields(scanner.Text())
|
||||||
|
if fields[0] == name {
|
||||||
|
if fields[2] != "0" {
|
||||||
|
state = inuse
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch fields[4] {
|
||||||
|
case "Live":
|
||||||
|
state = live
|
||||||
|
case "Loading":
|
||||||
|
state = loading
|
||||||
|
case "Unloading":
|
||||||
|
state = unloading
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return state, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadModuleWithDependencies(name, path string) error {
|
||||||
|
deps, err := getModuleDependencies(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't load list of module %s dependecies", name)
|
||||||
|
}
|
||||||
|
for _, dep := range deps {
|
||||||
|
err = loadModule(dep.name, dep.path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't load dependecy module %s for %s", dep.name, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loadModule(name, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadModule(name, path string) error {
|
||||||
|
state, err := moduleStatus(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if state >= loading {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := f.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed closing %s file, %v", path, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// first try finit_module(2), then init_module(2)
|
||||||
|
err = unix.FinitModule(int(f.Fd()), "", 0)
|
||||||
|
if errors.Is(err, unix.ENOSYS) {
|
||||||
|
buf, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return unix.InitModule(buf, "")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getModuleDependencies returns a module dependencies
|
||||||
|
func getModuleDependencies(name string) ([]module, error) {
|
||||||
|
f, err := os.Open(filepath.Join(moduleRoot, "/modules.dep"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := f.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed closing modules.dep file, %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var deps []string
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if pathToName(strings.TrimSuffix(fields[0], ":")) == name {
|
||||||
|
deps = fields
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(deps) == 0 {
|
||||||
|
return nil, ErrModuleNotFound
|
||||||
|
}
|
||||||
|
deps[0] = strings.TrimSuffix(deps[0], ":")
|
||||||
|
|
||||||
|
var modules []module
|
||||||
|
for _, v := range deps {
|
||||||
|
if pathToName(v) != name {
|
||||||
|
modules = append(modules, module{
|
||||||
|
name: pathToName(v),
|
||||||
|
path: filepath.Join(moduleRoot, v),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modules, nil
|
||||||
|
}
|
||||||
221
iface/module_linux_test.go
Normal file
221
iface/module_linux_test.go
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
package iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetModuleDependencies(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
module string
|
||||||
|
expected []module
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get Single Dependency",
|
||||||
|
module: "bar",
|
||||||
|
expected: []module{
|
||||||
|
{name: "foo", path: "kernel/a/foo.ko"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get Multiple Dependencies",
|
||||||
|
module: "baz",
|
||||||
|
expected: []module{
|
||||||
|
{name: "foo", path: "kernel/a/foo.ko"},
|
||||||
|
{name: "bar", path: "kernel/a/bar.ko"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get No Dependencies",
|
||||||
|
module: "foo",
|
||||||
|
expected: []module{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
defer resetGlobals()
|
||||||
|
_, _ = createFiles(t)
|
||||||
|
modules, err := getModuleDependencies(testCase.module)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := testCase.expected
|
||||||
|
for i := range expected {
|
||||||
|
expected[i].path = moduleRoot + "/" + expected[i].path
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ElementsMatchf(t, modules, expected, "returned modules should match")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsBuiltinModule(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
module string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Built In Should Return True",
|
||||||
|
module: "foo_bi",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Not Built In Should Return False",
|
||||||
|
module: "not_built_in",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
defer resetGlobals()
|
||||||
|
_, _ = createFiles(t)
|
||||||
|
|
||||||
|
isBuiltIn, err := isBuiltinModule(testCase.module)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, testCase.expected, isBuiltIn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModuleStatus(t *testing.T) {
|
||||||
|
random, err := getRandomLoadedModule(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("should be able to get random module")
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
module string
|
||||||
|
shouldBeLoaded bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Should Return Module Loading Or Greater Status",
|
||||||
|
module: random,
|
||||||
|
shouldBeLoaded: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Return Module Unloaded Or Lower Status",
|
||||||
|
module: "not_loaded_module",
|
||||||
|
shouldBeLoaded: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
defer resetGlobals()
|
||||||
|
_, _ = createFiles(t)
|
||||||
|
|
||||||
|
state, err := moduleStatus(testCase.module)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if testCase.shouldBeLoaded {
|
||||||
|
require.GreaterOrEqual(t, loading, state, "moduleStatus for %s should return state loading", testCase.module)
|
||||||
|
} else {
|
||||||
|
require.Less(t, state, loading, "module should return state unloading or lower")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetGlobals() {
|
||||||
|
moduleLibDir = defaultModuleDir
|
||||||
|
moduleRoot = getModuleRoot()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFiles(t *testing.T) (string, []module) {
|
||||||
|
writeFile := func(path, text string) {
|
||||||
|
if err := ioutil.WriteFile(path, []byte(text), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var u unix.Utsname
|
||||||
|
if err := unix.Uname(&u); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleLibDir = t.TempDir()
|
||||||
|
|
||||||
|
moduleRoot = getModuleRoot()
|
||||||
|
if err := os.Mkdir(moduleRoot, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
text := "kernel/a/foo.ko:\n"
|
||||||
|
text += "kernel/a/bar.ko: kernel/a/foo.ko\n"
|
||||||
|
text += "kernel/a/baz.ko: kernel/a/bar.ko kernel/a/foo.ko\n"
|
||||||
|
writeFile(filepath.Join(moduleRoot, "/modules.dep"), text)
|
||||||
|
|
||||||
|
text = "kernel/a/foo_bi.ko\n"
|
||||||
|
text += "kernel/a/bar-bi.ko.gz\n"
|
||||||
|
writeFile(filepath.Join(moduleRoot, "/modules.builtin"), text)
|
||||||
|
|
||||||
|
modules := []module{
|
||||||
|
{name: "foo", path: "kernel/a/foo.ko"},
|
||||||
|
{name: "bar", path: "kernel/a/bar.ko"},
|
||||||
|
{name: "baz", path: "kernel/a/baz.ko"},
|
||||||
|
}
|
||||||
|
return moduleLibDir, modules
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRandomLoadedModule(t *testing.T) (string, error) {
|
||||||
|
f, err := os.Open("/proc/modules")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := f.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed closing /proc/modules file, %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
lines, err := lineCounter(f)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
counter := 1
|
||||||
|
midLine := lines / 2
|
||||||
|
modName := ""
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
fields := strings.Fields(scanner.Text())
|
||||||
|
if counter == midLine {
|
||||||
|
if fields[4] == "Unloading" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
modName = fields[0]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
if scanner.Err() != nil {
|
||||||
|
return "", scanner.Err()
|
||||||
|
}
|
||||||
|
return modName, nil
|
||||||
|
}
|
||||||
|
func lineCounter(r io.Reader) (int, error) {
|
||||||
|
buf := make([]byte, 32*1024)
|
||||||
|
count := 0
|
||||||
|
lineSep := []byte{'\n'}
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, err := r.Read(buf)
|
||||||
|
count += bytes.Count(buf[:c], lineSep)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == io.EOF:
|
||||||
|
return count, nil
|
||||||
|
|
||||||
|
case err != nil:
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,13 +3,9 @@
|
|||||||
# Management API
|
# Management API
|
||||||
|
|
||||||
# Management API port
|
# Management API port
|
||||||
NETBIRD_MGMT_API_PORT=33071
|
NETBIRD_MGMT_API_PORT=33073
|
||||||
# Management GRPC API port
|
|
||||||
NETBIRD_MGMT_GRPC_API_PORT=33073
|
|
||||||
# Management API endpoint address, used by the Dashboard
|
# Management API endpoint address, used by the Dashboard
|
||||||
NETBIRD_MGMT_API_ENDPOINT=https://$NETBIRD_DOMAIN:$NETBIRD_MGMT_API_PORT
|
NETBIRD_MGMT_API_ENDPOINT=https://$NETBIRD_DOMAIN:$NETBIRD_MGMT_API_PORT
|
||||||
# Management GRPC API endpoint address, used by the hosts to register
|
|
||||||
NETBIRD_MGMT_GRPC_API_ENDPOINT=https://$NETBIRD_DOMAIN:$NETBIRD_MGMT_GRPC_API_PORT
|
|
||||||
# Management Certficate file path. These are generated by the Dashboard container
|
# Management Certficate file path. These are generated by the Dashboard container
|
||||||
NETBIRD_MGMT_API_CERT_FILE="/etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem"
|
NETBIRD_MGMT_API_CERT_FILE="/etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem"
|
||||||
# Management Certficate key file path.
|
# Management Certficate key file path.
|
||||||
@@ -31,18 +27,28 @@ MGMT_VOLUMESUFFIX="mgmt"
|
|||||||
SIGNAL_VOLUMESUFFIX="signal"
|
SIGNAL_VOLUMESUFFIX="signal"
|
||||||
LETSENCRYPT_VOLUMESUFFIX="letsencrypt"
|
LETSENCRYPT_VOLUMESUFFIX="letsencrypt"
|
||||||
|
|
||||||
|
NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="none"
|
||||||
|
|
||||||
|
NETBIRD_DISABLE_ANONYMOUS_METRICS=${NETBIRD_DISABLE_ANONYMOUS_METRICS:-false}
|
||||||
|
|
||||||
# exports
|
# exports
|
||||||
export NETBIRD_DOMAIN
|
export NETBIRD_DOMAIN
|
||||||
export NETBIRD_AUTH0_DOMAIN
|
export NETBIRD_AUTH_CLIENT_ID
|
||||||
export NETBIRD_AUTH0_CLIENT_ID
|
export NETBIRD_AUTH_AUDIENCE
|
||||||
export NETBIRD_AUTH0_AUDIENCE
|
export NETBIRD_AUTH_AUTHORITY
|
||||||
|
export NETBIRD_USE_AUTH0
|
||||||
|
export NETBIRD_AUTH_SUPPORTED_SCOPES
|
||||||
|
export NETBIRD_AUTH_JWT_CERTS
|
||||||
export NETBIRD_LETSENCRYPT_EMAIL
|
export NETBIRD_LETSENCRYPT_EMAIL
|
||||||
export NETBIRD_MGMT_API_PORT
|
export NETBIRD_MGMT_API_PORT
|
||||||
export NETBIRD_MGMT_API_ENDPOINT
|
export NETBIRD_MGMT_API_ENDPOINT
|
||||||
export NETBIRD_MGMT_GRPC_API_PORT
|
|
||||||
export NETBIRD_MGMT_GRPC_API_ENDPOINT
|
|
||||||
export NETBIRD_MGMT_API_CERT_FILE
|
export NETBIRD_MGMT_API_CERT_FILE
|
||||||
export NETBIRD_MGMT_API_CERT_KEY_FILE
|
export NETBIRD_MGMT_API_CERT_KEY_FILE
|
||||||
|
export NETBIRD_AUTH_DEVICE_AUTH_PROVIDER
|
||||||
|
export NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID
|
||||||
|
export NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT
|
||||||
|
export NETBIRD_AUTH_REDIRECT_URI
|
||||||
|
export NETBIRD_AUTH_SILENT_REDIRECT_URI
|
||||||
export TURN_USER
|
export TURN_USER
|
||||||
export TURN_PASSWORD
|
export TURN_PASSWORD
|
||||||
export TURN_MIN_PORT
|
export TURN_MIN_PORT
|
||||||
@@ -51,3 +57,4 @@ export VOLUME_PREFIX
|
|||||||
export MGMT_VOLUMESUFFIX
|
export MGMT_VOLUMESUFFIX
|
||||||
export SIGNAL_VOLUMESUFFIX
|
export SIGNAL_VOLUMESUFFIX
|
||||||
export LETSENCRYPT_VOLUMESUFFIX
|
export LETSENCRYPT_VOLUMESUFFIX
|
||||||
|
export NETBIRD_DISABLE_ANONYMOUS_METRICS
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
if ! which curl > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo "This script uses curl fetch OpenID configuration from IDP."
|
||||||
|
echo "Please install curl and re-run the script https://curl.se/"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! which jq > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo "This script uses jq to load OpenID configuration from IDP."
|
||||||
|
echo "Please install jq and re-run the script https://stedolan.github.io/jq/"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
source setup.env
|
source setup.env
|
||||||
source base.setup.env
|
source base.setup.env
|
||||||
|
|
||||||
@@ -34,7 +50,6 @@ fi
|
|||||||
if [[ $NETBIRD_DOMAIN == "localhost" || $NETBIRD_DOMAIN == "127.0.0.1" ]]
|
if [[ $NETBIRD_DOMAIN == "localhost" || $NETBIRD_DOMAIN == "127.0.0.1" ]]
|
||||||
then
|
then
|
||||||
export NETBIRD_MGMT_API_ENDPOINT=http://$NETBIRD_DOMAIN:$NETBIRD_MGMT_API_PORT
|
export NETBIRD_MGMT_API_ENDPOINT=http://$NETBIRD_DOMAIN:$NETBIRD_MGMT_API_PORT
|
||||||
export NETBIRD_MGMT_GRPC_API_ENDPOINT=http://$NETBIRD_DOMAIN:$NETBIRD_MGMT_GRPC_API_PORT
|
|
||||||
unset NETBIRD_MGMT_API_CERT_FILE
|
unset NETBIRD_MGMT_API_CERT_FILE
|
||||||
unset NETBIRD_MGMT_API_CERT_KEY_FILE
|
unset NETBIRD_MGMT_API_CERT_KEY_FILE
|
||||||
fi
|
fi
|
||||||
@@ -64,6 +79,49 @@ export MGMT_VOLUMENAME
|
|||||||
export SIGNAL_VOLUMENAME
|
export SIGNAL_VOLUMENAME
|
||||||
export LETSENCRYPT_VOLUMENAME
|
export LETSENCRYPT_VOLUMENAME
|
||||||
|
|
||||||
|
#backwards compatibility after migrating to generic OIDC with Auth0
|
||||||
|
if [[ -z "${NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT}" ]]; then
|
||||||
|
|
||||||
|
if [[ -z "${NETBIRD_AUTH0_DOMAIN}" ]]; then
|
||||||
|
# not a backward compatible state
|
||||||
|
echo "NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT property must be set in the setup.env file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "It seems like you provided an old setup.env file."
|
||||||
|
echo "Since the release of v0.8.10, we introduced a new set of properties."
|
||||||
|
echo "The script is backward compatible and will continue automatically."
|
||||||
|
echo "In the future versions it will be deprecated. Please refer to the documentation to learn about the changes http://netbird.io/docs/getting-started/self-hosting"
|
||||||
|
|
||||||
|
export NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT="https://${NETBIRD_AUTH0_DOMAIN}/.well-known/openid-configuration"
|
||||||
|
export NETBIRD_USE_AUTH0="true"
|
||||||
|
export NETBIRD_AUTH_AUDIENCE=${NETBIRD_AUTH0_AUDIENCE}
|
||||||
|
export NETBIRD_AUTH_CLIENT_ID=${NETBIRD_AUTH0_CLIENT_ID}
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "loading OpenID configuration from ${NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT} to the openid-configuration.json file"
|
||||||
|
curl "${NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT}" -q -o openid-configuration.json
|
||||||
|
|
||||||
|
export NETBIRD_AUTH_AUTHORITY=$( jq -r '.issuer' openid-configuration.json )
|
||||||
|
export NETBIRD_AUTH_JWT_CERTS=$( jq -r '.jwks_uri' openid-configuration.json )
|
||||||
|
export NETBIRD_AUTH_SUPPORTED_SCOPES=$( jq -r '.scopes_supported | join(" ")' openid-configuration.json )
|
||||||
|
export NETBIRD_AUTH_TOKEN_ENDPOINT=$( jq -r '.token_endpoint' openid-configuration.json )
|
||||||
|
export NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT=$( jq -r '.device_authorization_endpoint' openid-configuration.json )
|
||||||
|
|
||||||
|
if [ $NETBIRD_USE_AUTH0 == "true" ]
|
||||||
|
then
|
||||||
|
export NETBIRD_AUTH_SUPPORTED_SCOPES="openid profile email offline_access api email_verified"
|
||||||
|
else
|
||||||
|
export NETBIRD_AUTH_SUPPORTED_SCOPES="openid profile email offline_access api"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -z "${NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID}" ]]; then
|
||||||
|
# user enabled Device Authorization Grant feature
|
||||||
|
export NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="hosted"
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
@@ -8,14 +8,18 @@ services:
|
|||||||
- 80:80
|
- 80:80
|
||||||
- 443:443
|
- 443:443
|
||||||
environment:
|
environment:
|
||||||
- AUTH0_DOMAIN=$NETBIRD_AUTH0_DOMAIN
|
- AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
||||||
- AUTH0_CLIENT_ID=$NETBIRD_AUTH0_CLIENT_ID
|
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
||||||
- AUTH0_AUDIENCE=$NETBIRD_AUTH0_AUDIENCE
|
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
||||||
|
- USE_AUTH0=$NETBIRD_USE_AUTH0
|
||||||
|
- AUTH_SUPPORTED_SCOPES=$NETBIRD_AUTH_SUPPORTED_SCOPES
|
||||||
- NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
- NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
||||||
- NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_MGMT_GRPC_API_ENDPOINT
|
- NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
||||||
- NGINX_SSL_PORT=443
|
- NGINX_SSL_PORT=443
|
||||||
- LETSENCRYPT_DOMAIN=$NETBIRD_DOMAIN
|
- LETSENCRYPT_DOMAIN=$NETBIRD_DOMAIN
|
||||||
- LETSENCRYPT_EMAIL=$NETBIRD_LETSENCRYPT_EMAIL
|
- LETSENCRYPT_EMAIL=$NETBIRD_LETSENCRYPT_EMAIL
|
||||||
|
- AUTH_REDIRECT_URI=$NETBIRD_AUTH_REDIRECT_URI
|
||||||
|
- AUTH_SILENT_REDIRECT_URI=$NETBIRD_AUTH_SILENT_REDIRECT_URI
|
||||||
volumes:
|
volumes:
|
||||||
- $LETSENCRYPT_VOLUMENAME:/etc/letsencrypt/
|
- $LETSENCRYPT_VOLUMENAME:/etc/letsencrypt/
|
||||||
# Signal
|
# Signal
|
||||||
@@ -25,7 +29,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- $SIGNAL_VOLUMENAME:/var/lib/netbird
|
- $SIGNAL_VOLUMENAME:/var/lib/netbird
|
||||||
ports:
|
ports:
|
||||||
- 10000:10000
|
- 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_DOMAIN", "--log-file", "console"]
|
# command: ["--letsencrypt-domain", "$NETBIRD_DOMAIN", "--log-file", "console"]
|
||||||
@@ -40,11 +44,11 @@ services:
|
|||||||
- $LETSENCRYPT_VOLUMENAME:/etc/letsencrypt:ro
|
- $LETSENCRYPT_VOLUMENAME:/etc/letsencrypt:ro
|
||||||
- ./management.json:/etc/netbird/management.json
|
- ./management.json:/etc/netbird/management.json
|
||||||
ports:
|
ports:
|
||||||
- $NETBIRD_MGMT_GRPC_API_PORT:33073 #gRPC port
|
- $NETBIRD_MGMT_API_PORT:443 #API port
|
||||||
- $NETBIRD_MGMT_API_PORT:33071 #API port
|
|
||||||
# # port and command for Let's Encrypt validation without dashboard container
|
# # port and command for Let's Encrypt validation without dashboard container
|
||||||
# - 443:443
|
# - 443:443
|
||||||
# command: ["--letsencrypt-domain", "$NETBIRD_DOMAIN", "--log-file", "console"]
|
# command: ["--letsencrypt-domain", "$NETBIRD_DOMAIN", "--log-file", "console"]
|
||||||
|
command: ["--port", "443", "--log-file", "console", "--disable-anonymous-metrics=$NETBIRD_DISABLE_ANONYMOUS_METRICS"]
|
||||||
# Coturn
|
# Coturn
|
||||||
coturn:
|
coturn:
|
||||||
image: coturn/coturn
|
image: coturn/coturn
|
||||||
|
|||||||
@@ -29,13 +29,24 @@
|
|||||||
"Datadir": "",
|
"Datadir": "",
|
||||||
"HttpConfig": {
|
"HttpConfig": {
|
||||||
"Address": "0.0.0.0:$NETBIRD_MGMT_API_PORT",
|
"Address": "0.0.0.0:$NETBIRD_MGMT_API_PORT",
|
||||||
"AuthIssuer": "https://$NETBIRD_AUTH0_DOMAIN/",
|
"AuthIssuer": "$NETBIRD_AUTH_AUTHORITY",
|
||||||
"AuthAudience": "$NETBIRD_AUTH0_AUDIENCE",
|
"AuthAudience": "$NETBIRD_AUTH_AUDIENCE",
|
||||||
"AuthKeysLocation": "https://$NETBIRD_AUTH0_DOMAIN/.well-known/jwks.json",
|
"AuthKeysLocation": "$NETBIRD_AUTH_JWT_CERTS",
|
||||||
"CertFile":"$NETBIRD_MGMT_API_CERT_FILE",
|
"CertFile":"$NETBIRD_MGMT_API_CERT_FILE",
|
||||||
"CertKey":"$NETBIRD_MGMT_API_CERT_KEY_FILE"
|
"CertKey":"$NETBIRD_MGMT_API_CERT_KEY_FILE",
|
||||||
|
"OIDCConfigEndpoint":"$NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT"
|
||||||
},
|
},
|
||||||
"IdpManagerConfig": {
|
"IdpManagerConfig": {
|
||||||
"Manager": "none"
|
"Manager": "none"
|
||||||
}
|
},
|
||||||
|
"DeviceAuthorizationFlow": {
|
||||||
|
"Provider": "$NETBIRD_AUTH_DEVICE_AUTH_PROVIDER",
|
||||||
|
"ProviderConfig": {
|
||||||
|
"Audience": "$NETBIRD_AUTH_AUDIENCE",
|
||||||
|
"Domain": "$NETBIRD_AUTH0_DOMAIN",
|
||||||
|
"ClientID": "$NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID",
|
||||||
|
"TokenEndpoint": "$NETBIRD_AUTH_TOKEN_ENDPOINT",
|
||||||
|
"DeviceAuthEndpoint": "$NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,22 @@
|
|||||||
## example file, you can copy this file to setup.env and update its values
|
## example file, you can copy this file to setup.env and update its values
|
||||||
##
|
##
|
||||||
# Dashboard domain and auth0 configuration
|
|
||||||
|
|
||||||
# Dashboard domain. e.g. app.mydomain.com
|
# Dashboard domain. e.g. app.mydomain.com
|
||||||
NETBIRD_DOMAIN=""
|
NETBIRD_DOMAIN=""
|
||||||
# e.g. dev-24vkclam.us.auth0.com
|
# OIDC configuration e.g., https://example.eu.auth0.com/.well-known/openid-configuration
|
||||||
NETBIRD_AUTH0_DOMAIN=""
|
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT=""
|
||||||
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
|
NETBIRD_AUTH_AUDIENCE=""
|
||||||
NETBIRD_AUTH0_CLIENT_ID=""
|
# e.g. netbird-client
|
||||||
# e.g. https://app.mydomain.com/ or https://app.mydomain.com,
|
NETBIRD_AUTH_CLIENT_ID=""
|
||||||
# Make sure you used the exact same value for Identifier
|
# indicates whether to use Auth0 or not: true or false
|
||||||
# you used when creating your Auth0 API
|
NETBIRD_USE_AUTH0="false"
|
||||||
NETBIRD_AUTH0_AUDIENCE=""
|
NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="none"
|
||||||
|
NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID=""
|
||||||
# e.g. hello@mydomain.com
|
# e.g. hello@mydomain.com
|
||||||
NETBIRD_LETSENCRYPT_EMAIL=""
|
NETBIRD_LETSENCRYPT_EMAIL=""
|
||||||
|
# if your IDP provider doesn't support fragmented URIs, configure custom
|
||||||
|
# redirect and silent redirect URIs, these will be concatenated into your NETBIRD_DOMAIN domain.
|
||||||
|
# NETBIRD_AUTH_REDIRECT_URI="/peers"
|
||||||
|
# NETBIRD_AUTH_SILENT_REDIRECT_URI="/add-peers"
|
||||||
|
|
||||||
|
# Disable anonymous metrics collection, see more information at https://netbird.io/docs/FAQ/metrics-collection
|
||||||
|
NETBIRD_DISABLE_ANONYMOUS_METRICS=false
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
## example file, you can copy this file to setup.env and update its values
|
## example file, you can copy this file to setup.env and update its values
|
||||||
##
|
##
|
||||||
# Dashboard domain and auth0 configuration
|
|
||||||
|
|
||||||
# Dashboard domain. e.g. app.mydomain.com
|
# Dashboard domain. e.g. app.mydomain.com
|
||||||
NETBIRD_DOMAIN="localhost"
|
NETBIRD_DOMAIN="localhost"
|
||||||
# e.g. dev-24vkclam.us.auth0.com
|
# e.g. https://dev-24vkclam.us.auth0.com/ or https://YOUR-KEYCLOAK-HOST:8080/realms/netbird
|
||||||
NETBIRD_AUTH0_DOMAIN=$CI_NETBIRD_AUTH0_DOMAIN
|
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT="https://example.eu.auth0.com/.well-known/openid-configuration"
|
||||||
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
|
# e.g. netbird-client
|
||||||
NETBIRD_AUTH0_CLIENT_ID=$CI_NETBIRD_AUTH0_CLIENT_ID
|
NETBIRD_AUTH_CLIENT_ID=$CI_NETBIRD_AUTH_CLIENT_ID
|
||||||
# e.g. https://app.mydomain.com/ or https://app.mydomain.com,
|
# indicates whether to use Auth0 or not: true or false
|
||||||
# Make sure you used the exact same value for Identifier
|
NETBIRD_USE_AUTH0=$CI_NETBIRD_USE_AUTH0
|
||||||
# you used when creating your Auth0 API
|
NETBIRD_AUTH_AUDIENCE=$CI_NETBIRD_AUTH_AUDIENCE
|
||||||
NETBIRD_AUTH0_AUDIENCE=$CI_NETBIRD_AUTH0_AUDIENCE
|
|
||||||
# e.g. hello@mydomain.com
|
# e.g. hello@mydomain.com
|
||||||
NETBIRD_LETSENCRYPT_EMAIL=""
|
NETBIRD_LETSENCRYPT_EMAIL=""
|
||||||
|
NETBIRD_AUTH_REDIRECT_URI="/peers"
|
||||||
@@ -109,7 +109,9 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stream, err := c.connectToStream(*serverPubKey)
|
ctx, cancelStream := context.WithCancel(c.ctx)
|
||||||
|
defer cancelStream()
|
||||||
|
stream, err := c.connectToStream(ctx, *serverPubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("failed to open Management Service stream: %s", err)
|
log.Debugf("failed to open Management Service stream: %s", err)
|
||||||
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
||||||
@@ -145,7 +147,7 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GrpcClient) connectToStream(serverPubKey wgtypes.Key) (proto.ManagementService_SyncClient, error) {
|
func (c *GrpcClient) connectToStream(ctx context.Context, serverPubKey wgtypes.Key) (proto.ManagementService_SyncClient, error) {
|
||||||
req := &proto.SyncRequest{}
|
req := &proto.SyncRequest{}
|
||||||
|
|
||||||
myPrivateKey := c.key
|
myPrivateKey := c.key
|
||||||
@@ -156,9 +158,12 @@ func (c *GrpcClient) connectToStream(serverPubKey wgtypes.Key) (proto.Management
|
|||||||
log.Errorf("failed encrypting message: %s", err)
|
log.Errorf("failed encrypting message: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
syncReq := &proto.EncryptedMessage{WgPubKey: myPublicKey.String(), Body: encryptedReq}
|
syncReq := &proto.EncryptedMessage{WgPubKey: myPublicKey.String(), Body: encryptedReq}
|
||||||
return c.realClient.Sync(c.ctx, syncReq)
|
sync, err := c.realClient.Sync(ctx, syncReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sync, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GrpcClient) receiveEvents(stream proto.ManagementService_SyncClient, serverPubKey wgtypes.Key, msgHandler func(msg *proto.SyncResponse) error) error {
|
func (c *GrpcClient) receiveEvents(stream proto.ManagementService_SyncClient, serverPubKey wgtypes.Key, msgHandler func(msg *proto.SyncResponse) error) error {
|
||||||
|
|||||||
@@ -3,19 +3,27 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
httpapi "github.com/netbirdio/netbird/management/server/http"
|
||||||
|
"github.com/netbirdio/netbird/management/server/metrics"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/http"
|
|
||||||
"github.com/netbirdio/netbird/management/server/idp"
|
"github.com/netbirdio/netbird/management/server/idp"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
|
|
||||||
@@ -28,11 +36,16 @@ import (
|
|||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ManagementLegacyPort is the port that was used before by the Management gRPC server.
|
||||||
|
// It is used for backward compatibility now.
|
||||||
|
const ManagementLegacyPort = 33073
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mgmtPort int
|
mgmtPort int
|
||||||
mgmtLetsencryptDomain string
|
mgmtLetsencryptDomain string
|
||||||
certFile string
|
certFile string
|
||||||
certKey string
|
certKey string
|
||||||
|
config *server.Config
|
||||||
|
|
||||||
kaep = keepalive.EnforcementPolicy{
|
kaep = keepalive.EnforcementPolicy{
|
||||||
MinTime: 15 * time.Second,
|
MinTime: 15 * time.Second,
|
||||||
@@ -48,34 +61,55 @@ var (
|
|||||||
|
|
||||||
mgmtCmd = &cobra.Command{
|
mgmtCmd = &cobra.Command{
|
||||||
Use: "management",
|
Use: "management",
|
||||||
Short: "start Netbird Management Server",
|
Short: "start NetBird Management Server",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
// detect whether user specified a port
|
||||||
|
userPort := cmd.Flag("port").Changed
|
||||||
|
|
||||||
|
var err error
|
||||||
|
config, err = loadMgmtConfig(mgmtConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed reading provided config file: %s: %v", mgmtConfig, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsEnabled := false
|
||||||
|
if mgmtLetsencryptDomain != "" || (config.HttpConfig.CertFile != "" && config.HttpConfig.CertKey != "") {
|
||||||
|
tlsEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !userPort {
|
||||||
|
// different defaults for port when tls enabled/disabled
|
||||||
|
if tlsEnabled {
|
||||||
|
mgmtPort = 443
|
||||||
|
} else {
|
||||||
|
mgmtPort = 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
err := util.InitLog(logLevel, logFile)
|
err := util.InitLog(logLevel, logFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed initializing log %v", err)
|
return fmt.Errorf("failed initializing log %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handleRebrand(cmd)
|
err = handleRebrand(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to migrate files %v", err)
|
return fmt.Errorf("failed to migrate files %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
config, err := loadMgmtConfig(mgmtConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed reading provided config file: %s: %v", mgmtConfig, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = os.Stat(config.Datadir); os.IsNotExist(err) {
|
if _, err = os.Stat(config.Datadir); os.IsNotExist(err) {
|
||||||
err = os.MkdirAll(config.Datadir, os.ModeDir)
|
err = os.MkdirAll(config.Datadir, os.ModeDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed creating datadir: %s: %v", config.Datadir, err)
|
return fmt.Errorf("failed creating datadir: %s: %v", config.Datadir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := server.NewStore(config.Datadir)
|
store, err := server.NewStore(config.Datadir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err)
|
||||||
}
|
}
|
||||||
peersUpdateManager := server.NewPeersUpdateManager()
|
peersUpdateManager := server.NewPeersUpdateManager()
|
||||||
|
|
||||||
@@ -83,82 +117,209 @@ var (
|
|||||||
if config.IdpManagerConfig != nil {
|
if config.IdpManagerConfig != nil {
|
||||||
idpManager, err = idp.NewManager(*config.IdpManagerConfig)
|
idpManager, err = idp.NewManager(*config.IdpManagerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("failed retrieving a new idp manager with err: ", err)
|
return fmt.Errorf("failed retrieving a new idp manager with err: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager)
|
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("failed build default manager: ", err)
|
return fmt.Errorf("failed to build default manager: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var opts []grpc.ServerOption
|
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
|
|
||||||
var httpServer *http.Server
|
gRPCOpts := []grpc.ServerOption{grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp)}
|
||||||
|
var certManager *autocert.Manager
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
tlsEnabled := false
|
||||||
if config.HttpConfig.LetsEncryptDomain != "" {
|
if config.HttpConfig.LetsEncryptDomain != "" {
|
||||||
// automatically generate a new certificate with Let's Encrypt
|
certManager, err = encryption.CreateCertManager(config.Datadir, config.HttpConfig.LetsEncryptDomain)
|
||||||
certManager := encryption.CreateCertManager(config.Datadir, config.HttpConfig.LetsEncryptDomain)
|
|
||||||
transportCredentials := credentials.NewTLS(certManager.TLSConfig())
|
|
||||||
opts = append(opts, grpc.Creds(transportCredentials))
|
|
||||||
|
|
||||||
httpServer = http.NewHttpsServer(config.HttpConfig, certManager, accountManager)
|
|
||||||
} else if config.HttpConfig.CertFile != "" && config.HttpConfig.CertKey != "" {
|
|
||||||
// use provided certificate
|
|
||||||
tlsConfig, err := loadTLSConfig(config.HttpConfig.CertFile, config.HttpConfig.CertKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("cannot load TLS credentials: ", err)
|
return fmt.Errorf("failed creating LetsEncrypt cert manager: %v", err)
|
||||||
|
}
|
||||||
|
transportCredentials := credentials.NewTLS(certManager.TLSConfig())
|
||||||
|
gRPCOpts = append(gRPCOpts, grpc.Creds(transportCredentials))
|
||||||
|
tlsEnabled = true
|
||||||
|
} else if config.HttpConfig.CertFile != "" && config.HttpConfig.CertKey != "" {
|
||||||
|
tlsConfig, err = loadTLSConfig(config.HttpConfig.CertFile, config.HttpConfig.CertKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("cannot load TLS credentials: %v", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
transportCredentials := credentials.NewTLS(tlsConfig)
|
transportCredentials := credentials.NewTLS(tlsConfig)
|
||||||
opts = append(opts, grpc.Creds(transportCredentials))
|
gRPCOpts = append(gRPCOpts, grpc.Creds(transportCredentials))
|
||||||
httpServer = http.NewHttpsServerWithTLSConfig(config.HttpConfig, tlsConfig, accountManager)
|
tlsEnabled = true
|
||||||
} else {
|
|
||||||
// start server without SSL
|
|
||||||
httpServer = http.NewHttpServer(config.HttpConfig, accountManager)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opts = append(opts, grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
httpAPIHandler, err := httpapi.APIHandler(accountManager,
|
||||||
grpcServer := grpc.NewServer(opts...)
|
config.HttpConfig.AuthIssuer, config.HttpConfig.AuthAudience, config.HttpConfig.AuthKeysLocation)
|
||||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
|
||||||
server, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed creating new server: %v", err)
|
return fmt.Errorf("failed creating HTTP API handler: %v", err)
|
||||||
}
|
}
|
||||||
mgmtProto.RegisterManagementServiceServer(grpcServer, server)
|
|
||||||
log.Printf("started server: localhost:%v", mgmtPort)
|
|
||||||
|
|
||||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", mgmtPort))
|
gRPCAPIHandler := grpc.NewServer(gRPCOpts...)
|
||||||
|
srv, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to listen: %v", err)
|
return fmt.Errorf("failed creating gRPC API handler: %v", err)
|
||||||
|
}
|
||||||
|
mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv)
|
||||||
|
|
||||||
|
installationID, err := getInstallationID(store)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("cannot load TLS credentials: %v", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
fmt.Println("metrics ", disableMetrics)
|
||||||
if err = grpcServer.Serve(lis); err != nil {
|
|
||||||
log.Fatalf("failed to serve gRpc server: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
if !disableMetrics {
|
||||||
err = httpServer.Start()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
metricsWorker := metrics.NewWorker(ctx, installationID, store, peersUpdateManager)
|
||||||
|
go metricsWorker.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
var compatListener net.Listener
|
||||||
|
if mgmtPort != ManagementLegacyPort {
|
||||||
|
// The Management gRPC server was running on port 33073 previously. Old agents that are already connected to it
|
||||||
|
// are using port 33073. For compatibility purposes we keep running a 2nd gRPC server on port 33073.
|
||||||
|
compatListener, err = serveGRPC(gRPCAPIHandler, ManagementLegacyPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to serve http server: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
}()
|
log.Infof("running gRPC backward compatibility server: %s", compatListener.Addr().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
rootHandler := handlerFunc(gRPCAPIHandler, httpAPIHandler)
|
||||||
|
var listener net.Listener
|
||||||
|
if certManager != nil {
|
||||||
|
// a call to certManager.Listener() always creates a new listener so we do it once
|
||||||
|
cml := certManager.Listener()
|
||||||
|
if mgmtPort == 443 {
|
||||||
|
// CertManager, HTTP and gRPC API all on the same port
|
||||||
|
rootHandler = certManager.HTTPHandler(rootHandler)
|
||||||
|
listener = cml
|
||||||
|
} else {
|
||||||
|
listener, err = tls.Listen("tcp", fmt.Sprintf(":%d", mgmtPort), certManager.TLSConfig())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed creating TLS listener on port %d: %v", mgmtPort, err)
|
||||||
|
}
|
||||||
|
log.Infof("running HTTP server (LetsEncrypt challenge handler): %s", cml.Addr().String())
|
||||||
|
serveHTTP(cml, certManager.HTTPHandler(nil))
|
||||||
|
}
|
||||||
|
} else if tlsConfig != nil {
|
||||||
|
listener, err = tls.Listen("tcp", fmt.Sprintf(":%d", mgmtPort), tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed creating TLS listener on port %d: %v", mgmtPort, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
listener, err = net.Listen("tcp", fmt.Sprintf(":%d", mgmtPort))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed creating TCP listener on port %d: %v", mgmtPort, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("running HTTP server and gRPC server on the same port: %s", listener.Addr().String())
|
||||||
|
serveGRPCWithHTTP(listener, rootHandler, tlsEnabled)
|
||||||
|
|
||||||
SetupCloseHandler()
|
SetupCloseHandler()
|
||||||
<-stopCh
|
|
||||||
log.Println("Receive signal to stop running Management server")
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
err = httpServer.Stop(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed stopping the http server %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
grpcServer.Stop()
|
<-stopCh
|
||||||
|
_ = listener.Close()
|
||||||
|
if certManager != nil {
|
||||||
|
_ = certManager.Listener().Close()
|
||||||
|
}
|
||||||
|
gRPCAPIHandler.Stop()
|
||||||
|
log.Infof("stopped Management Service")
|
||||||
|
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func notifyStop(msg string) {
|
||||||
|
select {
|
||||||
|
case stopCh <- 1:
|
||||||
|
log.Error(msg)
|
||||||
|
default:
|
||||||
|
// stop has been already called, nothing to report
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInstallationID(store server.Store) (string, error) {
|
||||||
|
installationID := store.GetInstallationID()
|
||||||
|
if installationID != "" {
|
||||||
|
return installationID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
installationID = strings.ToUpper(uuid.New().String())
|
||||||
|
err := store.SaveInstallationID(installationID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return installationID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveGRPC(grpcServer *grpc.Server, port int) (net.Listener, error) {
|
||||||
|
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
err := grpcServer.Serve(listener)
|
||||||
|
if err != nil {
|
||||||
|
notifyStop(fmt.Sprintf("failed running gRPC server on port %d: %v", port, err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return listener, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveHTTP(httpListener net.Listener, handler http.Handler) {
|
||||||
|
go func() {
|
||||||
|
err := http.Serve(httpListener, handler)
|
||||||
|
if err != nil {
|
||||||
|
notifyStop(fmt.Sprintf("failed running HTTP server: %v", err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveGRPCWithHTTP(listener net.Listener, handler http.Handler, tlsEnabled bool) {
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
if tlsEnabled {
|
||||||
|
err = http.Serve(listener, handler)
|
||||||
|
} else {
|
||||||
|
// the following magic is needed to support HTTP2 without TLS
|
||||||
|
// and still share a single port between gRPC and HTTP APIs
|
||||||
|
h1s := &http.Server{
|
||||||
|
Handler: h2c.NewHandler(handler, &http2.Server{}),
|
||||||
|
}
|
||||||
|
err = h1s.Serve(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case stopCh <- 1:
|
||||||
|
log.Errorf("failed to serve HTTP and gRPC server: %v", err)
|
||||||
|
default:
|
||||||
|
// stop has been already called, nothing to report
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlerFunc(gRPCHandler *grpc.Server, httpHandler http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
grpcHeader := strings.HasPrefix(request.Header.Get("Content-Type"), "application/grpc") ||
|
||||||
|
strings.HasPrefix(request.Header.Get("Content-Type"), "application/grpc+proto")
|
||||||
|
if request.ProtoMajor == 2 && grpcHeader {
|
||||||
|
gRPCHandler.ServeHTTP(writer, request)
|
||||||
|
} else {
|
||||||
|
httpHandler.ServeHTTP(writer, request)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
||||||
config := &server.Config{}
|
config := &server.Config{}
|
||||||
_, err := util.ReadJson(mgmtConfigPath, config)
|
_, err := util.ReadJson(mgmtConfigPath, config)
|
||||||
@@ -177,9 +338,88 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
|||||||
config.HttpConfig.CertKey = certKey
|
config.HttpConfig.CertKey = certKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oidcEndpoint := config.HttpConfig.OIDCConfigEndpoint
|
||||||
|
if oidcEndpoint != "" {
|
||||||
|
// if OIDCConfigEndpoint is specified, we can load DeviceAuthEndpoint and TokenEndpoint automatically
|
||||||
|
log.Infof("loading OIDC configuration from the provided IDP configuration endpoint %s", oidcEndpoint)
|
||||||
|
oidcConfig, err := fetchOIDCConfig(oidcEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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",
|
||||||
|
oidcConfig.Issuer, config.HttpConfig.AuthIssuer)
|
||||||
|
config.HttpConfig.AuthIssuer = oidcConfig.Issuer
|
||||||
|
|
||||||
|
log.Infof("overriding HttpConfig.AuthKeysLocation (JWT certs) with a new value %s, previously configured value: %s",
|
||||||
|
oidcConfig.JwksURI, config.HttpConfig.AuthKeysLocation)
|
||||||
|
config.HttpConfig.AuthKeysLocation = oidcConfig.JwksURI
|
||||||
|
|
||||||
|
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",
|
||||||
|
oidcConfig.TokenEndpoint, config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint)
|
||||||
|
config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint
|
||||||
|
log.Infof("overriding DeviceAuthorizationFlow.DeviceAuthEndpoint with a new value: %s, previously configured value: %s",
|
||||||
|
oidcConfig.DeviceAuthEndpoint, config.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint)
|
||||||
|
config.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint = oidcConfig.DeviceAuthEndpoint
|
||||||
|
|
||||||
|
u, err := url.Parse(oidcEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Infof("overriding DeviceAuthorizationFlow.ProviderConfig.Domain with a new value: %s, previously configured value: %s",
|
||||||
|
u.Host, config.DeviceAuthorizationFlow.ProviderConfig.Domain)
|
||||||
|
config.DeviceAuthorizationFlow.ProviderConfig.Domain = u.Host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return config, err
|
return config, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OIDCConfigResponse used for parsing OIDC config response
|
||||||
|
type OIDCConfigResponse struct {
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
TokenEndpoint string `json:"token_endpoint"`
|
||||||
|
DeviceAuthEndpoint string `json:"device_authorization_endpoint"`
|
||||||
|
JwksURI string `json:"jwks_uri"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchOIDCConfig fetches OIDC configuration from the IDP
|
||||||
|
func fetchOIDCConfig(oidcEndpoint string) (OIDCConfigResponse, error) {
|
||||||
|
|
||||||
|
res, err := http.Get(oidcEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return OIDCConfigResponse{}, fmt.Errorf("failed fetching OIDC configuration fro mendpoint %s %v", oidcEndpoint, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed closing response body %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return OIDCConfigResponse{}, fmt.Errorf("failed reading OIDC configuration response body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return OIDCConfigResponse{}, fmt.Errorf("OIDC configuration request returned status %d with response: %s",
|
||||||
|
res.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
config := OIDCConfigResponse{}
|
||||||
|
err = json.Unmarshal(body, &config)
|
||||||
|
if err != nil {
|
||||||
|
return OIDCConfigResponse{}, fmt.Errorf("failed unmarshaling OIDC configuration response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func loadTLSConfig(certFile string, certKey string) (*tls.Config, error) {
|
func loadTLSConfig(certFile string, certKey string) (*tls.Config, error) {
|
||||||
// Load server's certificate and private key
|
// Load server's certificate and private key
|
||||||
serverCert, err := tls.LoadX509KeyPair(certFile, certKey)
|
serverCert, err := tls.LoadX509KeyPair(certFile, certKey)
|
||||||
@@ -191,6 +431,9 @@ func loadTLSConfig(certFile string, certKey string) (*tls.Config, error) {
|
|||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
Certificates: []tls.Certificate{serverCert},
|
Certificates: []tls.Certificate{serverCert},
|
||||||
ClientAuth: tls.NoClientCert,
|
ClientAuth: tls.NoClientCert,
|
||||||
|
NextProtos: []string{
|
||||||
|
"h2", "http/1.1", // enable HTTP/2
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
@@ -263,7 +506,7 @@ func copySymLink(source, dest string) error {
|
|||||||
|
|
||||||
func cpDir(src string, dst string) error {
|
func cpDir(src string, dst string) error {
|
||||||
var err error
|
var err error
|
||||||
var fds []os.FileInfo
|
var fds []os.DirEntry
|
||||||
var srcinfo os.FileInfo
|
var srcinfo os.FileInfo
|
||||||
|
|
||||||
if srcinfo, err = os.Stat(src); err != nil {
|
if srcinfo, err = os.Stat(src); err != nil {
|
||||||
@@ -274,7 +517,7 @@ func cpDir(src string, dst string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fds, err = ioutil.ReadDir(src); err != nil {
|
if fds, err = os.ReadDir(src); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, fd := range fds {
|
for _, fd := range fds {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ var (
|
|||||||
mgmtConfig string
|
mgmtConfig string
|
||||||
logLevel string
|
logLevel string
|
||||||
logFile string
|
logFile string
|
||||||
|
disableMetrics bool
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "netbird-mgmt",
|
Use: "netbird-mgmt",
|
||||||
@@ -60,12 +61,13 @@ func init() {
|
|||||||
oldDefaultMgmtConfig = oldDefaultMgmtConfigDir + "/management.json"
|
oldDefaultMgmtConfig = oldDefaultMgmtConfigDir + "/management.json"
|
||||||
oldDefaultLogFile = oldDefaultLogDir + "/management.log"
|
oldDefaultLogFile = oldDefaultLogDir + "/management.log"
|
||||||
|
|
||||||
mgmtCmd.Flags().IntVar(&mgmtPort, "port", 33073, "server port to listen on")
|
mgmtCmd.Flags().IntVar(&mgmtPort, "port", 80, "server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise")
|
||||||
mgmtCmd.Flags().StringVar(&mgmtDataDir, "datadir", defaultMgmtDataDir, "server data directory location")
|
mgmtCmd.Flags().StringVar(&mgmtDataDir, "datadir", defaultMgmtDataDir, "server data directory location")
|
||||||
mgmtCmd.Flags().StringVar(&mgmtConfig, "config", defaultMgmtConfig, "Netbird config file location. Config params specified via command line (e.g. datadir) have a precedence over configuration from this file")
|
mgmtCmd.Flags().StringVar(&mgmtConfig, "config", defaultMgmtConfig, "Netbird config file location. Config params specified via command line (e.g. datadir) have a precedence over configuration from this file")
|
||||||
mgmtCmd.Flags().StringVar(&mgmtLetsencryptDomain, "letsencrypt-domain", "", "a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS")
|
mgmtCmd.Flags().StringVar(&mgmtLetsencryptDomain, "letsencrypt-domain", "", "a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS")
|
||||||
mgmtCmd.Flags().StringVar(&certFile, "cert-file", "", "Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
|
mgmtCmd.Flags().StringVar(&certFile, "cert-file", "", "Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
|
||||||
mgmtCmd.Flags().StringVar(&certKey, "cert-key", "", "Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
|
mgmtCmd.Flags().StringVar(&certKey, "cert-key", "", "Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
|
||||||
|
mgmtCmd.Flags().BoolVar(&disableMetrics, "disable-anonymous-metrics", false, "disables push of anonymous usage metrics to NetBird")
|
||||||
rootCmd.MarkFlagRequired("config") //nolint
|
rootCmd.MarkFlagRequired("config") //nolint
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
|
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if ! which realpath > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo realpath is not installed
|
||||||
|
echo run: brew install coreutils
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
old_pwd=$(pwd)
|
||||||
|
script_path=$(dirname $(realpath "$0"))
|
||||||
|
cd "$script_path"
|
||||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
|
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
|
||||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
|
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
|
||||||
protoc -I proto/ proto/management.proto --go_out=. --go-grpc_out=.
|
protoc -I ./ ./management.proto --go_out=../ --go-grpc_out=../
|
||||||
|
cd "$old_pwd"
|
||||||
@@ -980,6 +980,8 @@ type NetworkMap struct {
|
|||||||
RemotePeers []*RemotePeerConfig `protobuf:"bytes,3,rep,name=remotePeers,proto3" json:"remotePeers,omitempty"`
|
RemotePeers []*RemotePeerConfig `protobuf:"bytes,3,rep,name=remotePeers,proto3" json:"remotePeers,omitempty"`
|
||||||
// Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
|
// Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
|
||||||
RemotePeersIsEmpty bool `protobuf:"varint,4,opt,name=remotePeersIsEmpty,proto3" json:"remotePeersIsEmpty,omitempty"`
|
RemotePeersIsEmpty bool `protobuf:"varint,4,opt,name=remotePeersIsEmpty,proto3" json:"remotePeersIsEmpty,omitempty"`
|
||||||
|
// List of routes to be applied
|
||||||
|
Routes []*Route `protobuf:"bytes,5,rep,name=Routes,proto3" json:"Routes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NetworkMap) Reset() {
|
func (x *NetworkMap) Reset() {
|
||||||
@@ -1042,6 +1044,13 @@ func (x *NetworkMap) GetRemotePeersIsEmpty() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *NetworkMap) GetRoutes() []*Route {
|
||||||
|
if x != nil {
|
||||||
|
return x.Routes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// RemotePeerConfig represents a configuration of a remote peer.
|
// RemotePeerConfig represents a configuration of a remote peer.
|
||||||
// The properties are used to configure Wireguard Peers sections
|
// The properties are used to configure Wireguard Peers sections
|
||||||
type RemotePeerConfig struct {
|
type RemotePeerConfig struct {
|
||||||
@@ -1278,9 +1287,14 @@ type ProviderConfig struct {
|
|||||||
// An IDP application client secret
|
// An IDP application client secret
|
||||||
ClientSecret string `protobuf:"bytes,2,opt,name=ClientSecret,proto3" json:"ClientSecret,omitempty"`
|
ClientSecret string `protobuf:"bytes,2,opt,name=ClientSecret,proto3" json:"ClientSecret,omitempty"`
|
||||||
// An IDP API domain
|
// An IDP API domain
|
||||||
|
// Deprecated. Use a DeviceAuthEndpoint and TokenEndpoint
|
||||||
Domain string `protobuf:"bytes,3,opt,name=Domain,proto3" json:"Domain,omitempty"`
|
Domain string `protobuf:"bytes,3,opt,name=Domain,proto3" json:"Domain,omitempty"`
|
||||||
// An Audience for validation
|
// An Audience for validation
|
||||||
Audience string `protobuf:"bytes,4,opt,name=Audience,proto3" json:"Audience,omitempty"`
|
Audience string `protobuf:"bytes,4,opt,name=Audience,proto3" json:"Audience,omitempty"`
|
||||||
|
// DeviceAuthEndpoint is an endpoint to request device authentication code.
|
||||||
|
DeviceAuthEndpoint string `protobuf:"bytes,5,opt,name=DeviceAuthEndpoint,proto3" json:"DeviceAuthEndpoint,omitempty"`
|
||||||
|
// TokenEndpoint is an endpoint to request auth token.
|
||||||
|
TokenEndpoint string `protobuf:"bytes,6,opt,name=TokenEndpoint,proto3" json:"TokenEndpoint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ProviderConfig) Reset() {
|
func (x *ProviderConfig) Reset() {
|
||||||
@@ -1343,6 +1357,116 @@ func (x *ProviderConfig) GetAudience() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ProviderConfig) GetDeviceAuthEndpoint() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.DeviceAuthEndpoint
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProviderConfig) GetTokenEndpoint() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.TokenEndpoint
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route represents a route.Route object
|
||||||
|
type Route struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||||
|
Network string `protobuf:"bytes,2,opt,name=Network,proto3" json:"Network,omitempty"`
|
||||||
|
NetworkType int64 `protobuf:"varint,3,opt,name=NetworkType,proto3" json:"NetworkType,omitempty"`
|
||||||
|
Peer string `protobuf:"bytes,4,opt,name=Peer,proto3" json:"Peer,omitempty"`
|
||||||
|
Metric int64 `protobuf:"varint,5,opt,name=Metric,proto3" json:"Metric,omitempty"`
|
||||||
|
Masquerade bool `protobuf:"varint,6,opt,name=Masquerade,proto3" json:"Masquerade,omitempty"`
|
||||||
|
NetID string `protobuf:"bytes,7,opt,name=NetID,proto3" json:"NetID,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Route) Reset() {
|
||||||
|
*x = Route{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_management_proto_msgTypes[19]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Route) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Route) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Route) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_management_proto_msgTypes[19]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Route.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Route) Descriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{19}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Route) GetID() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ID
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Route) GetNetwork() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Network
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Route) GetNetworkType() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.NetworkType
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Route) GetPeer() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Peer
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Route) GetMetric() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Metric
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Route) GetMasquerade() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Masquerade
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Route) GetNetID() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.NetID
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
var File_management_proto protoreflect.FileDescriptor
|
var File_management_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_management_proto_rawDesc = []byte{
|
var file_management_proto_rawDesc = []byte{
|
||||||
@@ -1459,7 +1583,7 @@ var file_management_proto_rawDesc = []byte{
|
|||||||
0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28,
|
0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||||
0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53,
|
0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53,
|
||||||
0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e,
|
0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e,
|
||||||
0x66, 0x69, 0x67, 0x22, 0xcc, 0x01, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d,
|
0x66, 0x69, 0x67, 0x22, 0xf7, 0x01, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d,
|
||||||
0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01,
|
0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01,
|
||||||
0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65,
|
0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65,
|
||||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
|
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
|
||||||
@@ -1472,67 +1596,87 @@ var file_management_proto_rawDesc = []byte{
|
|||||||
0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72,
|
0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72,
|
||||||
0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12,
|
0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12,
|
||||||
0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70,
|
0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70,
|
||||||
0x74, 0x79, 0x22, 0x83, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65,
|
0x74, 0x79, 0x12, 0x29, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03,
|
||||||
0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62,
|
0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||||
0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62,
|
0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0x83, 0x01,
|
||||||
0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70,
|
0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
|
||||||
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64,
|
0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01,
|
||||||
0x49, 0x70, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e,
|
||||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03,
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73,
|
0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x12, 0x33,
|
||||||
0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x49, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43,
|
0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62,
|
0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53,
|
||||||
0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e,
|
0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e,
|
||||||
0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b,
|
0x66, 0x69, 0x67, 0x22, 0x49, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62,
|
0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01,
|
||||||
0x4b, 0x65, 0x79, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74,
|
0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
|
||||||
0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65,
|
0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20,
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65,
|
0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x20,
|
||||||
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f,
|
0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
|
||||||
0x77, 0x12, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20,
|
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f,
|
||||||
0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
|
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x48, 0x0a, 0x08,
|
||||||
0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
|
0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c,
|
||||||
0x72, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x50,
|
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x76, 0x69,
|
||||||
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20,
|
0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46,
|
||||||
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x08, 0x50, 0x72,
|
||||||
0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
|
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64,
|
||||||
0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22,
|
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
|
||||||
0x16, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x48,
|
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76,
|
||||||
0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x84, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76,
|
0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76,
|
||||||
0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c,
|
0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, 0x0a, 0x08, 0x70, 0x72,
|
||||||
0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c,
|
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44,
|
||||||
0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
0x10, 0x00, 0x22, 0xda, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43,
|
||||||
0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
|
||||||
0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f,
|
0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
|
||||||
0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61,
|
0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65,
|
||||||
0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04,
|
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x32, 0xf7,
|
0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
|
||||||
0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72,
|
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a,
|
||||||
0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e,
|
0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
|
0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x44, 0x65, 0x76,
|
||||||
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61,
|
0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,
|
||||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
|
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74,
|
||||||
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53,
|
0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x6f, 0x6b,
|
||||||
0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
|
||||||
|
0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22,
|
||||||
|
0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18,
|
||||||
|
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65, 0x74,
|
||||||
|
0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77,
|
||||||
|
0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79,
|
||||||
|
0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
|
||||||
|
0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x04, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, 0x74,
|
||||||
|
0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69,
|
||||||
|
0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x18,
|
||||||
|
0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64,
|
||||||
|
0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
|
||||||
|
0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x32, 0xf7, 0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61,
|
||||||
|
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a,
|
||||||
|
0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
|
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
||||||
|
0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
|
0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
||||||
|
0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d,
|
||||||
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
|
||||||
|
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e,
|
||||||
|
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
||||||
|
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c,
|
||||||
|
0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d,
|
||||||
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
|
||||||
|
0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72,
|
||||||
|
0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||||
|
0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e,
|
||||||
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||||
|
0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d,
|
||||||
|
0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69,
|
||||||
|
0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46,
|
||||||
|
0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||||
0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
|
0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
|
||||||
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22,
|
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22,
|
||||||
0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||||
0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x74, 0x6f, 0x33,
|
||||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73,
|
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61,
|
|
||||||
0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
|
||||||
0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
|
||||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a,
|
|
||||||
0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
|
|
||||||
0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e,
|
|
||||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
|
||||||
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d,
|
|
||||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f,
|
|
||||||
0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -1548,7 +1692,7 @@ func file_management_proto_rawDescGZIP() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||||
var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
|
var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 20)
|
||||||
var file_management_proto_goTypes = []interface{}{
|
var file_management_proto_goTypes = []interface{}{
|
||||||
(HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol
|
(HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol
|
||||||
(DeviceAuthorizationFlowProvider)(0), // 1: management.DeviceAuthorizationFlow.provider
|
(DeviceAuthorizationFlowProvider)(0), // 1: management.DeviceAuthorizationFlow.provider
|
||||||
@@ -1571,7 +1715,8 @@ var file_management_proto_goTypes = []interface{}{
|
|||||||
(*DeviceAuthorizationFlowRequest)(nil), // 18: management.DeviceAuthorizationFlowRequest
|
(*DeviceAuthorizationFlowRequest)(nil), // 18: management.DeviceAuthorizationFlowRequest
|
||||||
(*DeviceAuthorizationFlow)(nil), // 19: management.DeviceAuthorizationFlow
|
(*DeviceAuthorizationFlow)(nil), // 19: management.DeviceAuthorizationFlow
|
||||||
(*ProviderConfig)(nil), // 20: management.ProviderConfig
|
(*ProviderConfig)(nil), // 20: management.ProviderConfig
|
||||||
(*timestamp.Timestamp)(nil), // 21: google.protobuf.Timestamp
|
(*Route)(nil), // 21: management.Route
|
||||||
|
(*timestamp.Timestamp)(nil), // 22: google.protobuf.Timestamp
|
||||||
}
|
}
|
||||||
var file_management_proto_depIdxs = []int32{
|
var file_management_proto_depIdxs = []int32{
|
||||||
11, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
11, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
||||||
@@ -1582,7 +1727,7 @@ var file_management_proto_depIdxs = []int32{
|
|||||||
6, // 5: management.LoginRequest.peerKeys:type_name -> management.PeerKeys
|
6, // 5: management.LoginRequest.peerKeys:type_name -> management.PeerKeys
|
||||||
11, // 6: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
11, // 6: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
||||||
14, // 7: management.LoginResponse.peerConfig:type_name -> management.PeerConfig
|
14, // 7: management.LoginResponse.peerConfig:type_name -> management.PeerConfig
|
||||||
21, // 8: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp
|
22, // 8: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp
|
||||||
12, // 9: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig
|
12, // 9: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig
|
||||||
13, // 10: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig
|
13, // 10: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig
|
||||||
12, // 11: management.WiretrusteeConfig.signal:type_name -> management.HostConfig
|
12, // 11: management.WiretrusteeConfig.signal:type_name -> management.HostConfig
|
||||||
@@ -1591,24 +1736,25 @@ var file_management_proto_depIdxs = []int32{
|
|||||||
17, // 14: management.PeerConfig.sshConfig:type_name -> management.SSHConfig
|
17, // 14: management.PeerConfig.sshConfig:type_name -> management.SSHConfig
|
||||||
14, // 15: management.NetworkMap.peerConfig:type_name -> management.PeerConfig
|
14, // 15: management.NetworkMap.peerConfig:type_name -> management.PeerConfig
|
||||||
16, // 16: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig
|
16, // 16: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig
|
||||||
17, // 17: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig
|
21, // 17: management.NetworkMap.Routes:type_name -> management.Route
|
||||||
1, // 18: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider
|
17, // 18: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig
|
||||||
20, // 19: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
|
1, // 19: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider
|
||||||
2, // 20: management.ManagementService.Login:input_type -> management.EncryptedMessage
|
20, // 20: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
|
||||||
2, // 21: management.ManagementService.Sync:input_type -> management.EncryptedMessage
|
2, // 21: management.ManagementService.Login:input_type -> management.EncryptedMessage
|
||||||
10, // 22: management.ManagementService.GetServerKey:input_type -> management.Empty
|
2, // 22: management.ManagementService.Sync:input_type -> management.EncryptedMessage
|
||||||
10, // 23: management.ManagementService.isHealthy:input_type -> management.Empty
|
10, // 23: management.ManagementService.GetServerKey:input_type -> management.Empty
|
||||||
2, // 24: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage
|
10, // 24: management.ManagementService.isHealthy:input_type -> management.Empty
|
||||||
2, // 25: management.ManagementService.Login:output_type -> management.EncryptedMessage
|
2, // 25: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage
|
||||||
2, // 26: management.ManagementService.Sync:output_type -> management.EncryptedMessage
|
2, // 26: management.ManagementService.Login:output_type -> management.EncryptedMessage
|
||||||
9, // 27: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
|
2, // 27: management.ManagementService.Sync:output_type -> management.EncryptedMessage
|
||||||
10, // 28: management.ManagementService.isHealthy:output_type -> management.Empty
|
9, // 28: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
|
||||||
2, // 29: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage
|
10, // 29: management.ManagementService.isHealthy:output_type -> management.Empty
|
||||||
25, // [25:30] is the sub-list for method output_type
|
2, // 30: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage
|
||||||
20, // [20:25] is the sub-list for method input_type
|
26, // [26:31] is the sub-list for method output_type
|
||||||
20, // [20:20] is the sub-list for extension type_name
|
21, // [21:26] is the sub-list for method input_type
|
||||||
20, // [20:20] is the sub-list for extension extendee
|
21, // [21:21] is the sub-list for extension type_name
|
||||||
0, // [0:20] is the sub-list for field type_name
|
21, // [21:21] is the sub-list for extension extendee
|
||||||
|
0, // [0:21] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_management_proto_init() }
|
func init() { file_management_proto_init() }
|
||||||
@@ -1845,6 +1991,18 @@ func file_management_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
file_management_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Route); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
@@ -1852,7 +2010,7 @@ func file_management_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_management_proto_rawDesc,
|
RawDescriptor: file_management_proto_rawDesc,
|
||||||
NumEnums: 2,
|
NumEnums: 2,
|
||||||
NumMessages: 19,
|
NumMessages: 20,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -176,6 +176,8 @@ message NetworkMap {
|
|||||||
// Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
|
// Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
|
||||||
bool remotePeersIsEmpty = 4;
|
bool remotePeersIsEmpty = 4;
|
||||||
|
|
||||||
|
// List of routes to be applied
|
||||||
|
repeated Route Routes = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemotePeerConfig represents a configuration of a remote peer.
|
// RemotePeerConfig represents a configuration of a remote peer.
|
||||||
@@ -225,7 +227,23 @@ message ProviderConfig {
|
|||||||
// An IDP application client secret
|
// An IDP application client secret
|
||||||
string ClientSecret = 2;
|
string ClientSecret = 2;
|
||||||
// An IDP API domain
|
// An IDP API domain
|
||||||
string Domain =3;
|
// Deprecated. Use a DeviceAuthEndpoint and TokenEndpoint
|
||||||
|
string Domain = 3;
|
||||||
// An Audience for validation
|
// An Audience for validation
|
||||||
string Audience = 4;
|
string Audience = 4;
|
||||||
|
// DeviceAuthEndpoint is an endpoint to request device authentication code.
|
||||||
|
string DeviceAuthEndpoint = 5;
|
||||||
|
// TokenEndpoint is an endpoint to request auth token.
|
||||||
|
string TokenEndpoint = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Route represents a route.Route object
|
||||||
|
message Route {
|
||||||
|
string ID = 1;
|
||||||
|
string Network = 2;
|
||||||
|
int64 NetworkType = 3;
|
||||||
|
string Peer = 4;
|
||||||
|
int64 Metric = 5;
|
||||||
|
bool Masquerade = 6;
|
||||||
|
string NetID = 7;
|
||||||
|
}
|
||||||
@@ -3,10 +3,12 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/eko/gocache/v2/cache"
|
"github.com/eko/gocache/v3/cache"
|
||||||
cacheStore "github.com/eko/gocache/v2/store"
|
cacheStore "github.com/eko/gocache/v3/store"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/server/idp"
|
"github.com/netbirdio/netbird/management/server/idp"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
gocache "github.com/patrickmn/go-cache"
|
gocache "github.com/patrickmn/go-cache"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -14,6 +16,7 @@ import (
|
|||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -27,20 +30,29 @@ const (
|
|||||||
CacheExpirationMin = 3 * 24 * 3600 * time.Second // 3 days
|
CacheExpirationMin = 3 * 24 * 3600 * time.Second // 3 days
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func cacheEntryExpiration() time.Duration {
|
||||||
|
r := rand.Intn(int(CacheExpirationMax.Milliseconds()-CacheExpirationMin.Milliseconds())) + int(CacheExpirationMin.Milliseconds())
|
||||||
|
return time.Duration(r) * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
type AccountManager interface {
|
type AccountManager interface {
|
||||||
GetOrCreateAccountByUser(userId, domain string) (*Account, error)
|
GetOrCreateAccountByUser(userId, domain string) (*Account, error)
|
||||||
GetAccountByUser(userId string) (*Account, error)
|
GetAccountByUser(userId string) (*Account, error)
|
||||||
AddSetupKey(
|
CreateSetupKey(
|
||||||
accountId string,
|
accountId string,
|
||||||
keyName string,
|
keyName string,
|
||||||
keyType SetupKeyType,
|
keyType SetupKeyType,
|
||||||
expiresIn time.Duration,
|
expiresIn time.Duration,
|
||||||
|
autoGroups []string,
|
||||||
) (*SetupKey, error)
|
) (*SetupKey, error)
|
||||||
RevokeSetupKey(accountId string, keyId string) (*SetupKey, error)
|
SaveSetupKey(accountID string, key *SetupKey) (*SetupKey, error)
|
||||||
RenameSetupKey(accountId string, keyId string, newName string) (*SetupKey, error)
|
CreateUser(accountID string, key *UserInfo) (*UserInfo, error)
|
||||||
|
ListSetupKeys(accountID string) ([]*SetupKey, error)
|
||||||
|
SaveUser(accountID string, key *User) (*UserInfo, error)
|
||||||
|
GetSetupKey(accountID, keyID string) (*SetupKey, error)
|
||||||
GetAccountById(accountId string) (*Account, error)
|
GetAccountById(accountId string) (*Account, error)
|
||||||
GetAccountByUserOrAccountId(userId, accountId, domain string) (*Account, error)
|
GetAccountByUserOrAccountId(userId, accountId, domain string) (*Account, error)
|
||||||
GetAccountWithAuthorizationClaims(claims jwtclaims.AuthorizationClaims) (*Account, error)
|
GetAccountFromToken(claims jwtclaims.AuthorizationClaims) (*Account, error)
|
||||||
IsUserAdmin(claims jwtclaims.AuthorizationClaims) (bool, error)
|
IsUserAdmin(claims jwtclaims.AuthorizationClaims) (bool, error)
|
||||||
AccountExists(accountId string) (*bool, error)
|
AccountExists(accountId string) (*bool, error)
|
||||||
GetPeer(peerKey string) (*Peer, error)
|
GetPeer(peerKey string) (*Peer, error)
|
||||||
@@ -48,6 +60,7 @@ type AccountManager interface {
|
|||||||
RenamePeer(accountId string, peerKey string, newName string) (*Peer, error)
|
RenamePeer(accountId string, peerKey string, newName string) (*Peer, error)
|
||||||
DeletePeer(accountId string, peerKey string) (*Peer, error)
|
DeletePeer(accountId string, peerKey string) (*Peer, error)
|
||||||
GetPeerByIP(accountId string, peerIP string) (*Peer, error)
|
GetPeerByIP(accountId string, peerIP string) (*Peer, error)
|
||||||
|
UpdatePeer(accountID string, peer *Peer) (*Peer, error)
|
||||||
GetNetworkMap(peerKey string) (*NetworkMap, error)
|
GetNetworkMap(peerKey string) (*NetworkMap, error)
|
||||||
GetPeerNetwork(peerKey string) (*Network, error)
|
GetPeerNetwork(peerKey string) (*Network, error)
|
||||||
AddPeer(setupKey string, userId string, peer *Peer) (*Peer, error)
|
AddPeer(setupKey string, userId string, peer *Peer) (*Peer, error)
|
||||||
@@ -67,16 +80,31 @@ type AccountManager interface {
|
|||||||
UpdateRule(accountID string, ruleID string, operations []RuleUpdateOperation) (*Rule, error)
|
UpdateRule(accountID string, ruleID string, operations []RuleUpdateOperation) (*Rule, error)
|
||||||
DeleteRule(accountId, ruleID string) error
|
DeleteRule(accountId, ruleID string) error
|
||||||
ListRules(accountId string) ([]*Rule, error)
|
ListRules(accountId string) ([]*Rule, error)
|
||||||
UpdatePeer(accountID string, peer *Peer) (*Peer, error)
|
GetRoute(accountID, routeID string) (*route.Route, error)
|
||||||
|
CreateRoute(accountID string, prefix, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error)
|
||||||
|
SaveRoute(accountID string, route *route.Route) error
|
||||||
|
UpdateRoute(accountID string, routeID string, operations []RouteUpdateOperation) (*route.Route, error)
|
||||||
|
DeleteRoute(accountID, routeID string) error
|
||||||
|
ListRoutes(accountID string) ([]*route.Route, error)
|
||||||
|
GetNameServerGroup(accountID, nsGroupID string) (*nbdns.NameServerGroup, error)
|
||||||
|
CreateNameServerGroup(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, enabled bool) (*nbdns.NameServerGroup, error)
|
||||||
|
SaveNameServerGroup(accountID string, nsGroupToSave *nbdns.NameServerGroup) error
|
||||||
|
UpdateNameServerGroup(accountID, nsGroupID string, operations []NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error)
|
||||||
|
DeleteNameServerGroup(accountID, nsGroupID string) error
|
||||||
|
ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultAccountManager struct {
|
type DefaultAccountManager struct {
|
||||||
Store Store
|
Store Store
|
||||||
// mutex to synchronise account operations (e.g. generating Peer IP address inside the Network)
|
// mux to synchronise account operations (e.g. generating Peer IP address inside the Network)
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
|
// cacheMux and cacheLoading helps to make sure that only a single cache reload runs at a time per accountID
|
||||||
|
cacheMux sync.Mutex
|
||||||
|
// cacheLoading keeps the accountIDs that are currently reloading. The accountID has to be removed once cache has been reloaded
|
||||||
|
cacheLoading map[string]chan struct{}
|
||||||
peersUpdateManager *PeersUpdateManager
|
peersUpdateManager *PeersUpdateManager
|
||||||
idpManager idp.Manager
|
idpManager idp.Manager
|
||||||
cacheManager cache.CacheInterface
|
cacheManager cache.CacheInterface[[]*idp.UserData]
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,13 +122,17 @@ type Account struct {
|
|||||||
Users map[string]*User
|
Users map[string]*User
|
||||||
Groups map[string]*Group
|
Groups map[string]*Group
|
||||||
Rules map[string]*Rule
|
Rules map[string]*Rule
|
||||||
|
Routes map[string]*route.Route
|
||||||
|
NameServerGroups map[string]*nbdns.NameServerGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
Status string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Account) Copy() *Account {
|
func (a *Account) Copy() *Account {
|
||||||
@@ -129,15 +161,27 @@ func (a *Account) Copy() *Account {
|
|||||||
rules[id] = rule.Copy()
|
rules[id] = rule.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
routes := map[string]*route.Route{}
|
||||||
|
for id, route := range a.Routes {
|
||||||
|
routes[id] = route.Copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroups := map[string]*nbdns.NameServerGroup{}
|
||||||
|
for id, nsGroup := range a.NameServerGroups {
|
||||||
|
nsGroups[id] = nsGroup.Copy()
|
||||||
|
}
|
||||||
|
|
||||||
return &Account{
|
return &Account{
|
||||||
Id: a.Id,
|
Id: a.Id,
|
||||||
CreatedBy: a.CreatedBy,
|
CreatedBy: a.CreatedBy,
|
||||||
SetupKeys: setupKeys,
|
SetupKeys: setupKeys,
|
||||||
Network: a.Network.Copy(),
|
Network: a.Network.Copy(),
|
||||||
Peers: peers,
|
Peers: peers,
|
||||||
Users: users,
|
Users: users,
|
||||||
Groups: groups,
|
Groups: groups,
|
||||||
Rules: rules,
|
Rules: rules,
|
||||||
|
Routes: routes,
|
||||||
|
NameServerGroups: nsGroups,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +204,8 @@ func BuildManager(
|
|||||||
peersUpdateManager: peersUpdateManager,
|
peersUpdateManager: peersUpdateManager,
|
||||||
idpManager: idpManager,
|
idpManager: idpManager,
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
|
cacheMux: sync.Mutex{},
|
||||||
|
cacheLoading: map[string]chan struct{}{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// if account has not default group
|
// if account has not default group
|
||||||
@@ -176,9 +222,9 @@ func BuildManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
gocacheClient := gocache.New(CacheExpirationMax, 30*time.Minute)
|
gocacheClient := gocache.New(CacheExpirationMax, 30*time.Minute)
|
||||||
gocacheStore := cacheStore.NewGoCache(gocacheClient, nil)
|
gocacheStore := cacheStore.NewGoCache(gocacheClient)
|
||||||
|
|
||||||
am.cacheManager = cache.NewLoadable(am.loadFromCache, cache.New(gocacheStore))
|
am.cacheManager = cache.NewLoadable[[]*idp.UserData](am.loadAccount, cache.New[[]*idp.UserData](gocacheStore))
|
||||||
|
|
||||||
if !isNil(am.idpManager) {
|
if !isNil(am.idpManager) {
|
||||||
go func() {
|
go func() {
|
||||||
@@ -223,11 +269,7 @@ func (am *DefaultAccountManager) warmupIDPCache() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for accountID, users := range userData {
|
for accountID, users := range userData {
|
||||||
rand.Seed(time.Now().UnixNano())
|
err = am.cacheManager.Set(am.ctx, accountID, users, cacheStore.WithExpiration(cacheEntryExpiration()))
|
||||||
|
|
||||||
r := rand.Intn(int(CacheExpirationMax.Milliseconds()-CacheExpirationMin.Milliseconds())) + int(CacheExpirationMin.Milliseconds())
|
|
||||||
expiration := time.Duration(r) * time.Millisecond
|
|
||||||
err = am.cacheManager.Set(am.ctx, accountID, users, &cacheStore.Options{Expiration: expiration})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -236,93 +278,6 @@ func (am *DefaultAccountManager) warmupIDPCache() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSetupKey generates a new setup key with a given name and type, and adds it to the specified account
|
|
||||||
func (am *DefaultAccountManager) AddSetupKey(
|
|
||||||
accountId string,
|
|
||||||
keyName string,
|
|
||||||
keyType SetupKeyType,
|
|
||||||
expiresIn time.Duration,
|
|
||||||
) (*SetupKey, error) {
|
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
|
||||||
|
|
||||||
keyDuration := DefaultSetupKeyDuration
|
|
||||||
if expiresIn != 0 {
|
|
||||||
keyDuration = expiresIn
|
|
||||||
}
|
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
setupKey := GenerateSetupKey(keyName, keyType, keyDuration)
|
|
||||||
account.SetupKeys[setupKey.Key] = setupKey
|
|
||||||
|
|
||||||
err = am.Store.SaveAccount(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed adding account key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return setupKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RevokeSetupKey marks SetupKey as revoked - becomes not valid anymore
|
|
||||||
func (am *DefaultAccountManager) RevokeSetupKey(accountId string, keyId string) (*SetupKey, error) {
|
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
setupKey := getAccountSetupKeyById(account, keyId)
|
|
||||||
if setupKey == nil {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", keyId)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyCopy := setupKey.Copy()
|
|
||||||
keyCopy.Revoked = true
|
|
||||||
account.SetupKeys[keyCopy.Key] = keyCopy
|
|
||||||
err = am.Store.SaveAccount(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed adding account key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyCopy, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameSetupKey renames existing setup key of the specified account.
|
|
||||||
func (am *DefaultAccountManager) RenameSetupKey(
|
|
||||||
accountId string,
|
|
||||||
keyId string,
|
|
||||||
newName string,
|
|
||||||
) (*SetupKey, error) {
|
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
setupKey := getAccountSetupKeyById(account, keyId)
|
|
||||||
if setupKey == nil {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", keyId)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyCopy := setupKey.Copy()
|
|
||||||
keyCopy.Name = newName
|
|
||||||
account.SetupKeys[keyCopy.Key] = keyCopy
|
|
||||||
err = am.Store.SaveAccount(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed adding account key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyCopy, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountById returns an existing account using its ID or error (NotFound) if doesn't exist
|
// GetAccountById returns an existing account using its ID or error (NotFound) if doesn't exist
|
||||||
func (am *DefaultAccountManager) GetAccountById(accountId string) (*Account, error) {
|
func (am *DefaultAccountManager) GetAccountById(accountId string) (*Account, error) {
|
||||||
am.mux.Lock()
|
am.mux.Lock()
|
||||||
@@ -348,7 +303,7 @@ func (am *DefaultAccountManager) GetAccountByUserOrAccountId(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.NotFound, "account not found using user id: %s", userId)
|
return nil, status.Errorf(codes.NotFound, "account not found using user id: %s", userId)
|
||||||
}
|
}
|
||||||
err = am.updateIDPMetadata(userId, account.Id)
|
err = am.addAccountIDToIDPAppMeta(userId, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -362,10 +317,28 @@ func isNil(i idp.Manager) bool {
|
|||||||
return i == nil || reflect.ValueOf(i).IsNil()
|
return i == nil || reflect.ValueOf(i).IsNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateIDPMetadata update user's app metadata in idp manager
|
// addAccountIDToIDPAppMeta update user's app metadata in idp manager
|
||||||
func (am *DefaultAccountManager) updateIDPMetadata(userId, accountID string) error {
|
func (am *DefaultAccountManager) addAccountIDToIDPAppMeta(userID string, account *Account) error {
|
||||||
if !isNil(am.idpManager) {
|
if !isNil(am.idpManager) {
|
||||||
err := am.idpManager.UpdateUserAppMetadata(userId, idp.AppMetadata{WTAccountId: accountID})
|
|
||||||
|
// user can be nil if it wasn't found (e.g., just created)
|
||||||
|
user, err := am.lookupUserInCache(userID, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user != nil && user.AppMetadata.WTAccountID == account.Id {
|
||||||
|
// it was already set, so we skip the unnecessary update
|
||||||
|
log.Debugf("skipping IDP App Meta update because accountID %s has been already set for user %s",
|
||||||
|
account.Id, userID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.idpManager.UpdateUserAppMetadata(userID, idp.AppMetadata{WTAccountID: account.Id})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(
|
return status.Errorf(
|
||||||
codes.Internal,
|
codes.Internal,
|
||||||
@@ -373,39 +346,113 @@ func (am *DefaultAccountManager) updateIDPMetadata(userId, accountID string) err
|
|||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// refresh cache to reflect the update
|
||||||
|
_, err = am.refreshCache(account.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeLocalAndQueryUser(queried idp.UserData, local User) *UserInfo {
|
func (am *DefaultAccountManager) loadAccount(_ context.Context, accountID interface{}) ([]*idp.UserData, error) {
|
||||||
return &UserInfo{
|
log.Debugf("account %s not found in cache, reloading", accountID)
|
||||||
ID: local.Id,
|
|
||||||
Email: queried.Email,
|
|
||||||
Name: queried.Name,
|
|
||||||
Role: string(local.Role),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *DefaultAccountManager) loadFromCache(_ context.Context, accountID interface{}) (interface{}, error) {
|
|
||||||
return am.idpManager.GetAccount(fmt.Sprintf("%v", accountID))
|
return am.idpManager.GetAccount(fmt.Sprintf("%v", accountID))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) lookupCache(accountUsers map[string]*User, accountID string) ([]*idp.UserData, error) {
|
func (am *DefaultAccountManager) lookupUserInCacheByEmail(email string, accountID string) (*idp.UserData, error) {
|
||||||
data, err := am.cacheManager.Get(am.ctx, accountID)
|
data, err := am.getAccountFromCache(accountID, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userData := data.([]*idp.UserData)
|
for _, datum := range data {
|
||||||
|
if datum.Email == email {
|
||||||
|
return datum, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookupUserInCache looks up user in the IdP cache and returns it. If the user wasn't found, the function returns nil
|
||||||
|
func (am *DefaultAccountManager) lookupUserInCache(userID string, account *Account) (*idp.UserData, error) {
|
||||||
|
users := make(map[string]struct{}, len(account.Users))
|
||||||
|
for _, user := range account.Users {
|
||||||
|
users[user.Id] = struct{}{}
|
||||||
|
}
|
||||||
|
log.Debugf("looking up user %s of account %s in cache", userID, account.Id)
|
||||||
|
userData, err := am.lookupCache(users, account.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, datum := range userData {
|
||||||
|
if datum.ID == userID {
|
||||||
|
return datum, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *DefaultAccountManager) refreshCache(accountID string) ([]*idp.UserData, error) {
|
||||||
|
return am.getAccountFromCache(accountID, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAccountFromCache returns user data for a given account ensuring that cache load happens only once
|
||||||
|
func (am *DefaultAccountManager) getAccountFromCache(accountID string, forceReload bool) ([]*idp.UserData, error) {
|
||||||
|
am.cacheMux.Lock()
|
||||||
|
loadingChan := am.cacheLoading[accountID]
|
||||||
|
if loadingChan == nil {
|
||||||
|
loadingChan = make(chan struct{})
|
||||||
|
am.cacheLoading[accountID] = loadingChan
|
||||||
|
am.cacheMux.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
am.cacheMux.Lock()
|
||||||
|
delete(am.cacheLoading, accountID)
|
||||||
|
close(loadingChan)
|
||||||
|
am.cacheMux.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if forceReload {
|
||||||
|
err := am.cacheManager.Delete(am.ctx, accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return am.cacheManager.Get(am.ctx, accountID)
|
||||||
|
}
|
||||||
|
am.cacheMux.Unlock()
|
||||||
|
|
||||||
|
log.Debugf("one request to get account %s is already running", accountID)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-loadingChan:
|
||||||
|
// channel has been closed meaning cache was loaded => simply return from cache
|
||||||
|
return am.cacheManager.Get(am.ctx, accountID)
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
return nil, fmt.Errorf("timeout while waiting for account %s cache to reload", accountID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *DefaultAccountManager) lookupCache(accountUsers map[string]struct{}, accountID string) ([]*idp.UserData, error) {
|
||||||
|
data, err := am.getAccountFromCache(accountID, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
userDataMap := make(map[string]struct{})
|
userDataMap := make(map[string]struct{})
|
||||||
for _, datum := range userData {
|
for _, datum := range data {
|
||||||
userDataMap[datum.ID] = struct{}{}
|
userDataMap[datum.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether we need to reload the cache
|
// check whether we need to reload the cache
|
||||||
// the accountUsers ID list is the source of truth and all the users should be in the cache
|
// the accountUsers ID list is the source of truth and all the users should be in the cache
|
||||||
reload := len(accountUsers) != len(userData)
|
reload := len(accountUsers) != len(data)
|
||||||
for user := range accountUsers {
|
for user := range accountUsers {
|
||||||
if _, ok := userDataMap[user]; !ok {
|
if _, ok := userDataMap[user]; !ok {
|
||||||
reload = true
|
reload = true
|
||||||
@@ -414,59 +461,13 @@ func (am *DefaultAccountManager) lookupCache(accountUsers map[string]*User, acco
|
|||||||
|
|
||||||
if reload {
|
if reload {
|
||||||
// reload cache once avoiding loops
|
// reload cache once avoiding loops
|
||||||
err := am.cacheManager.Delete(am.ctx, accountID)
|
data, err = am.refreshCache(accountID)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data, err = am.cacheManager.Get(am.ctx, accountID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
userData = data.([]*idp.UserData)
|
|
||||||
}
|
|
||||||
|
|
||||||
return userData, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsersFromAccount performs a batched request for users from IDP by account id
|
|
||||||
func (am *DefaultAccountManager) GetUsersFromAccount(accountID string) ([]*UserInfo, error) {
|
|
||||||
account, err := am.GetAccountById(accountID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
queriedUsers := make([]*idp.UserData, 0)
|
|
||||||
if !isNil(am.idpManager) {
|
|
||||||
queriedUsers, err = am.lookupCache(account.Users, accountID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userInfo := make([]*UserInfo, 0)
|
return data, err
|
||||||
|
|
||||||
// in case of self-hosted, or IDP doesn't return anything, we will return the locally stored userInfo
|
|
||||||
if len(queriedUsers) == 0 {
|
|
||||||
for _, user := range account.Users {
|
|
||||||
userInfo = append(userInfo, &UserInfo{
|
|
||||||
ID: user.Id,
|
|
||||||
Email: "",
|
|
||||||
Name: "",
|
|
||||||
Role: string(user.Role),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return userInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, queriedUser := range queriedUsers {
|
|
||||||
if localUser, contains := account.Users[queriedUser.ID]; contains {
|
|
||||||
userInfo = append(userInfo, mergeLocalAndQueryUser(*queriedUser, *localUser))
|
|
||||||
log.Debugf("Merged userinfo to send back; %v", userInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return userInfo, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateAccountDomainAttributes updates the account domain attributes and then, saves the account
|
// updateAccountDomainAttributes updates the account domain attributes and then, saves the account
|
||||||
@@ -496,7 +497,6 @@ func (am *DefaultAccountManager) updateAccountDomainAttributes(
|
|||||||
|
|
||||||
// handleExistingUserAccount handles existing User accounts and update its domain attributes.
|
// handleExistingUserAccount handles existing User accounts and update its domain attributes.
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// If there is no primary domain account yet, we set the account as primary for the domain. Otherwise,
|
// If there is no primary domain account yet, we set the account as primary for the domain. Otherwise,
|
||||||
// we compare the account's ID with the domain account ID, and if they don't match, we set the account as
|
// we compare the account's ID with the domain account ID, and if they don't match, we set the account as
|
||||||
// non-primary account for the domain. We don't merge accounts at this stage, because of cases when a domain
|
// non-primary account for the domain. We don't merge accounts at this stage, because of cases when a domain
|
||||||
@@ -522,7 +522,7 @@ func (am *DefaultAccountManager) handleExistingUserAccount(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// we should register the account ID to this user's metadata in our IDP manager
|
// we should register the account ID to this user's metadata in our IDP manager
|
||||||
err = am.updateIDPMetadata(claims.UserId, existingAcc.Id)
|
err = am.addAccountIDToIDPAppMeta(claims.UserId, existingAcc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -560,7 +560,7 @@ func (am *DefaultAccountManager) handleNewUserAccount(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = am.updateIDPMetadata(claims.UserId, account.Id)
|
err = am.addAccountIDToIDPAppMeta(claims.UserId, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -568,7 +568,61 @@ func (am *DefaultAccountManager) handleNewUserAccount(
|
|||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountWithAuthorizationClaims retrievs an account using JWT Claims.
|
// redeemInvite checks whether user has been invited and redeems the invite
|
||||||
|
func (am *DefaultAccountManager) redeemInvite(account *Account, userID string) error {
|
||||||
|
// only possible with the enabled IdP manager
|
||||||
|
if am.idpManager == nil {
|
||||||
|
log.Warnf("invites only work with enabled IdP manager")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := am.lookupUserInCache(userID, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
return status.Errorf(codes.NotFound, "user %s not found in the IdP", userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.AppMetadata.WTPendingInvite {
|
||||||
|
log.Infof("redeeming invite for user %s account %s", userID, account.Id)
|
||||||
|
// User has already logged in, meaning that IdP should have set wt_pending_invite to false.
|
||||||
|
// Our job is to just reload cache.
|
||||||
|
go func() {
|
||||||
|
// set AppMetadata setting WTPendingInvite = false to indicate that it was redeemed
|
||||||
|
// it shouldn't be necessary since the IdP should redeem on login,
|
||||||
|
// but we make sure we do it if IdP is not capable of resetting teh flag.
|
||||||
|
_ = am.idpManager.UpdateUserAppMetadata(userID, idp.AppMetadata{WTAccountID: account.Id, WTPendingInvite: false}) //nolint
|
||||||
|
|
||||||
|
_, err = am.refreshCache(account.Id)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed reloading cache when redeeming user %s under account %s", userID, account.Id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("user %s of account %s redeemed invite", user.ID, account.Id)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountFromToken returns an account associated with this token
|
||||||
|
func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.AuthorizationClaims) (*Account, error) {
|
||||||
|
account, err := am.getAccountWithAuthorizationClaims(claims)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.redeemInvite(account, claims.UserId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAccountWithAuthorizationClaims retrievs an account using JWT Claims.
|
||||||
// if domain is of the PrivateCategory category, it will evaluate
|
// if domain is of the PrivateCategory category, it will evaluate
|
||||||
// if account is new, existing or if there is another account with the same domain
|
// if account is new, existing or if there is another account with the same domain
|
||||||
//
|
//
|
||||||
@@ -585,12 +639,12 @@ func (am *DefaultAccountManager) handleNewUserAccount(
|
|||||||
// Existing user + Existing account + Existing Indexed Domain -> Nothing changes
|
// Existing user + Existing account + Existing Indexed Domain -> Nothing changes
|
||||||
//
|
//
|
||||||
// Existing user + Existing account + Existing domain reclassified Domain as private -> Nothing changes (index domain)
|
// Existing user + Existing account + Existing domain reclassified Domain as private -> Nothing changes (index domain)
|
||||||
func (am *DefaultAccountManager) GetAccountWithAuthorizationClaims(
|
func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(
|
||||||
claims jwtclaims.AuthorizationClaims,
|
claims jwtclaims.AuthorizationClaims,
|
||||||
) (*Account, error) {
|
) (*Account, error) {
|
||||||
// if Account ID is part of the claims
|
// if Account ID is part of the claims
|
||||||
// it means that we've already classified the domain and user has an account
|
// it means that we've already classified the domain and user has an account
|
||||||
if claims.DomainCategory != PrivateCategory {
|
if claims.DomainCategory != PrivateCategory || !isDomainValid(claims.Domain) {
|
||||||
return am.GetAccountByUserOrAccountId(claims.UserId, claims.AccountId, claims.Domain)
|
return am.GetAccountByUserOrAccountId(claims.UserId, claims.AccountId, claims.Domain)
|
||||||
} else if claims.AccountId != "" {
|
} else if claims.AccountId != "" {
|
||||||
accountFromID, err := am.GetAccountById(claims.AccountId)
|
accountFromID, err := am.GetAccountById(claims.AccountId)
|
||||||
@@ -630,6 +684,11 @@ func (am *DefaultAccountManager) GetAccountWithAuthorizationClaims(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isDomainValid(domain string) bool {
|
||||||
|
re := regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`)
|
||||||
|
return re.Match([]byte(domain))
|
||||||
|
}
|
||||||
|
|
||||||
// AccountExists checks whether account exists (returns true) or not (returns false)
|
// AccountExists checks whether account exists (returns true) or not (returns false)
|
||||||
func (am *DefaultAccountManager) AccountExists(accountId string) (*bool, error) {
|
func (am *DefaultAccountManager) AccountExists(accountId string) (*bool, error) {
|
||||||
am.mux.Lock()
|
am.mux.Lock()
|
||||||
@@ -680,38 +739,33 @@ func newAccountWithId(accountId, userId, domain string) *Account {
|
|||||||
|
|
||||||
setupKeys := make(map[string]*SetupKey)
|
setupKeys := make(map[string]*SetupKey)
|
||||||
defaultKey := GenerateDefaultSetupKey()
|
defaultKey := GenerateDefaultSetupKey()
|
||||||
oneOffKey := GenerateSetupKey("One-off key", SetupKeyOneOff, DefaultSetupKeyDuration)
|
oneOffKey := GenerateSetupKey("One-off key", SetupKeyOneOff, DefaultSetupKeyDuration, []string{})
|
||||||
setupKeys[defaultKey.Key] = defaultKey
|
setupKeys[defaultKey.Key] = defaultKey
|
||||||
setupKeys[oneOffKey.Key] = oneOffKey
|
setupKeys[oneOffKey.Key] = oneOffKey
|
||||||
network := NewNetwork()
|
network := NewNetwork()
|
||||||
peers := make(map[string]*Peer)
|
peers := make(map[string]*Peer)
|
||||||
users := make(map[string]*User)
|
users := make(map[string]*User)
|
||||||
|
routes := make(map[string]*route.Route)
|
||||||
|
nameServersGroups := make(map[string]*nbdns.NameServerGroup)
|
||||||
users[userId] = NewAdminUser(userId)
|
users[userId] = NewAdminUser(userId)
|
||||||
log.Debugf("created new account %s with setup key %s", accountId, defaultKey.Key)
|
log.Debugf("created new account %s with setup key %s", accountId, defaultKey.Key)
|
||||||
|
|
||||||
acc := &Account{
|
acc := &Account{
|
||||||
Id: accountId,
|
Id: accountId,
|
||||||
SetupKeys: setupKeys,
|
SetupKeys: setupKeys,
|
||||||
Network: network,
|
Network: network,
|
||||||
Peers: peers,
|
Peers: peers,
|
||||||
Users: users,
|
Users: users,
|
||||||
CreatedBy: userId,
|
CreatedBy: userId,
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
|
Routes: routes,
|
||||||
|
NameServerGroups: nameServersGroups,
|
||||||
}
|
}
|
||||||
|
|
||||||
addAllGroup(acc)
|
addAllGroup(acc)
|
||||||
return acc
|
return acc
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAccountSetupKeyById(acc *Account, keyId string) *SetupKey {
|
|
||||||
for _, k := range acc.SetupKeys {
|
|
||||||
if keyId == k.Id {
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAccountSetupKeyByKey(acc *Account, key string) *SetupKey {
|
func getAccountSetupKeyByKey(acc *Account, key string) *SetupKey {
|
||||||
for _, k := range acc.SetupKeys {
|
for _, k := range acc.SetupKeys {
|
||||||
if key == k.Key {
|
if key == k.Key {
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ func TestAccountManager_GetOrCreateAccountByUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) {
|
||||||
type initUserParams jwtclaims.AuthorizationClaims
|
type initUserParams jwtclaims.AuthorizationClaims
|
||||||
|
|
||||||
type test struct {
|
type test struct {
|
||||||
@@ -140,6 +140,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
expectedMSG string
|
expectedMSG string
|
||||||
expectedUserRole UserRole
|
expectedUserRole UserRole
|
||||||
expectedDomainCategory string
|
expectedDomainCategory string
|
||||||
|
expectedDomain string
|
||||||
expectedPrimaryDomainStatus bool
|
expectedPrimaryDomainStatus bool
|
||||||
expectedCreatedBy string
|
expectedCreatedBy string
|
||||||
expectedUsers []string
|
expectedUsers []string
|
||||||
@@ -168,6 +169,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
expectedMSG: "account IDs shouldn't match",
|
expectedMSG: "account IDs shouldn't match",
|
||||||
expectedUserRole: UserRoleAdmin,
|
expectedUserRole: UserRoleAdmin,
|
||||||
expectedDomainCategory: "",
|
expectedDomainCategory: "",
|
||||||
|
expectedDomain: publicDomain,
|
||||||
expectedPrimaryDomainStatus: false,
|
expectedPrimaryDomainStatus: false,
|
||||||
expectedCreatedBy: "pub-domain-user",
|
expectedCreatedBy: "pub-domain-user",
|
||||||
expectedUsers: []string{"pub-domain-user"},
|
expectedUsers: []string{"pub-domain-user"},
|
||||||
@@ -188,6 +190,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
testingFunc: require.NotEqual,
|
testingFunc: require.NotEqual,
|
||||||
expectedMSG: "account IDs shouldn't match",
|
expectedMSG: "account IDs shouldn't match",
|
||||||
expectedUserRole: UserRoleAdmin,
|
expectedUserRole: UserRoleAdmin,
|
||||||
|
expectedDomain: unknownDomain,
|
||||||
expectedDomainCategory: "",
|
expectedDomainCategory: "",
|
||||||
expectedPrimaryDomainStatus: false,
|
expectedPrimaryDomainStatus: false,
|
||||||
expectedCreatedBy: "unknown-domain-user",
|
expectedCreatedBy: "unknown-domain-user",
|
||||||
@@ -205,6 +208,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
testingFunc: require.NotEqual,
|
testingFunc: require.NotEqual,
|
||||||
expectedMSG: "account IDs shouldn't match",
|
expectedMSG: "account IDs shouldn't match",
|
||||||
expectedUserRole: UserRoleAdmin,
|
expectedUserRole: UserRoleAdmin,
|
||||||
|
expectedDomain: privateDomain,
|
||||||
expectedDomainCategory: PrivateCategory,
|
expectedDomainCategory: PrivateCategory,
|
||||||
expectedPrimaryDomainStatus: true,
|
expectedPrimaryDomainStatus: true,
|
||||||
expectedCreatedBy: "pvt-domain-user",
|
expectedCreatedBy: "pvt-domain-user",
|
||||||
@@ -227,6 +231,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
testingFunc: require.Equal,
|
testingFunc: require.Equal,
|
||||||
expectedMSG: "account IDs should match",
|
expectedMSG: "account IDs should match",
|
||||||
expectedUserRole: UserRoleUser,
|
expectedUserRole: UserRoleUser,
|
||||||
|
expectedDomain: privateDomain,
|
||||||
expectedDomainCategory: PrivateCategory,
|
expectedDomainCategory: PrivateCategory,
|
||||||
expectedPrimaryDomainStatus: true,
|
expectedPrimaryDomainStatus: true,
|
||||||
expectedCreatedBy: defaultInitAccount.UserId,
|
expectedCreatedBy: defaultInitAccount.UserId,
|
||||||
@@ -244,6 +249,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
testingFunc: require.Equal,
|
testingFunc: require.Equal,
|
||||||
expectedMSG: "account IDs should match",
|
expectedMSG: "account IDs should match",
|
||||||
expectedUserRole: UserRoleAdmin,
|
expectedUserRole: UserRoleAdmin,
|
||||||
|
expectedDomain: defaultInitAccount.Domain,
|
||||||
expectedDomainCategory: PrivateCategory,
|
expectedDomainCategory: PrivateCategory,
|
||||||
expectedPrimaryDomainStatus: true,
|
expectedPrimaryDomainStatus: true,
|
||||||
expectedCreatedBy: defaultInitAccount.UserId,
|
expectedCreatedBy: defaultInitAccount.UserId,
|
||||||
@@ -262,12 +268,32 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
testingFunc: require.Equal,
|
testingFunc: require.Equal,
|
||||||
expectedMSG: "account IDs should match",
|
expectedMSG: "account IDs should match",
|
||||||
expectedUserRole: UserRoleAdmin,
|
expectedUserRole: UserRoleAdmin,
|
||||||
|
expectedDomain: defaultInitAccount.Domain,
|
||||||
expectedDomainCategory: PrivateCategory,
|
expectedDomainCategory: PrivateCategory,
|
||||||
expectedPrimaryDomainStatus: true,
|
expectedPrimaryDomainStatus: true,
|
||||||
expectedCreatedBy: defaultInitAccount.UserId,
|
expectedCreatedBy: defaultInitAccount.UserId,
|
||||||
expectedUsers: []string{defaultInitAccount.UserId},
|
expectedUsers: []string{defaultInitAccount.UserId},
|
||||||
}
|
}
|
||||||
for _, testCase := range []test{testCase1, testCase2, testCase3, testCase4, testCase5, testCase6} {
|
|
||||||
|
testCase7 := test{
|
||||||
|
name: "User With Private Category And Empty Domain",
|
||||||
|
inputClaims: jwtclaims.AuthorizationClaims{
|
||||||
|
Domain: "",
|
||||||
|
UserId: "pvt-domain-user",
|
||||||
|
DomainCategory: PrivateCategory,
|
||||||
|
},
|
||||||
|
inputInitUserParams: defaultInitAccount,
|
||||||
|
testingFunc: require.NotEqual,
|
||||||
|
expectedMSG: "account IDs shouldn't match",
|
||||||
|
expectedUserRole: UserRoleAdmin,
|
||||||
|
expectedDomain: "",
|
||||||
|
expectedDomainCategory: "",
|
||||||
|
expectedPrimaryDomainStatus: false,
|
||||||
|
expectedCreatedBy: "pvt-domain-user",
|
||||||
|
expectedUsers: []string{"pvt-domain-user"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range []test{testCase1, testCase2, testCase3, testCase4, testCase5, testCase6, testCase7} {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
manager, err := createManager(t)
|
manager, err := createManager(t)
|
||||||
require.NoError(t, err, "unable to create account manager")
|
require.NoError(t, err, "unable to create account manager")
|
||||||
@@ -284,7 +310,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
testCase.inputClaims.AccountId = initAccount.Id
|
testCase.inputClaims.AccountId = initAccount.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := manager.GetAccountWithAuthorizationClaims(testCase.inputClaims)
|
account, err := manager.GetAccountFromToken(testCase.inputClaims)
|
||||||
require.NoError(t, err, "support function failed")
|
require.NoError(t, err, "support function failed")
|
||||||
verifyNewAccountHasDefaultFields(t, account, testCase.expectedCreatedBy, testCase.inputClaims.Domain, testCase.expectedUsers)
|
verifyNewAccountHasDefaultFields(t, account, testCase.expectedCreatedBy, testCase.inputClaims.Domain, testCase.expectedUsers)
|
||||||
verifyCanAddPeerToAccount(t, manager, account, testCase.expectedCreatedBy)
|
verifyCanAddPeerToAccount(t, manager, account, testCase.expectedCreatedBy)
|
||||||
@@ -294,6 +320,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
require.EqualValues(t, testCase.expectedUserRole, account.Users[testCase.inputClaims.UserId].Role, "expected user role should match")
|
require.EqualValues(t, testCase.expectedUserRole, account.Users[testCase.inputClaims.UserId].Role, "expected user role should match")
|
||||||
require.EqualValues(t, testCase.expectedDomainCategory, account.DomainCategory, "expected account domain category should match")
|
require.EqualValues(t, testCase.expectedDomainCategory, account.DomainCategory, "expected account domain category should match")
|
||||||
require.EqualValues(t, testCase.expectedPrimaryDomainStatus, account.IsDomainPrimaryAccount, "expected account primary status should match")
|
require.EqualValues(t, testCase.expectedPrimaryDomainStatus, account.IsDomainPrimaryAccount, "expected account primary status should match")
|
||||||
|
require.EqualValues(t, testCase.expectedDomain, account.Domain, "expected account domain should match")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const (
|
|||||||
TCP Protocol = "tcp"
|
TCP Protocol = "tcp"
|
||||||
HTTP Protocol = "http"
|
HTTP Protocol = "http"
|
||||||
HTTPS Protocol = "https"
|
HTTPS Protocol = "https"
|
||||||
AUTH0 Provider = "auth0"
|
NONE Provider = "none"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config of the Management service
|
// Config of the Management service
|
||||||
@@ -49,13 +49,14 @@ type HttpServerConfig struct {
|
|||||||
CertFile string
|
CertFile string
|
||||||
//CertKey is the location of the certificate private key
|
//CertKey is the location of the certificate private key
|
||||||
CertKey string
|
CertKey string
|
||||||
Address string
|
|
||||||
// AuthAudience identifies the recipients that the JWT is intended for (aud in JWT)
|
// AuthAudience identifies the recipients that the JWT is intended for (aud in JWT)
|
||||||
AuthAudience string
|
AuthAudience string
|
||||||
// AuthIssuer identifies principal that issued the JWT.
|
// AuthIssuer identifies principal that issued the JWT.
|
||||||
AuthIssuer string
|
AuthIssuer string
|
||||||
// AuthKeysLocation is a location of JWT key set containing the public keys used to verify JWT
|
// AuthKeysLocation is a location of JWT key set containing the public keys used to verify JWT
|
||||||
AuthKeysLocation string
|
AuthKeysLocation string
|
||||||
|
// OIDCConfigEndpoint is the endpoint of an IDP manager to get OIDC configuration
|
||||||
|
OIDCConfigEndpoint string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Host represents a Wiretrustee host (e.g. STUN, TURN, Signal)
|
// Host represents a Wiretrustee host (e.g. STUN, TURN, Signal)
|
||||||
@@ -82,9 +83,14 @@ type ProviderConfig struct {
|
|||||||
// ClientSecret An IDP application client secret
|
// ClientSecret An IDP application client secret
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
// Domain An IDP API domain
|
// Domain An IDP API domain
|
||||||
|
// Deprecated. Use TokenEndpoint and DeviceAuthEndpoint
|
||||||
Domain string
|
Domain string
|
||||||
// Audience An Audience for to authorization validation
|
// Audience An Audience for to authorization validation
|
||||||
Audience string
|
Audience string
|
||||||
|
// TokenEndpoint is the endpoint of an IDP manager where clients can obtain access token
|
||||||
|
TokenEndpoint string
|
||||||
|
// DeviceAuthEndpoint is the endpoint of an IDP manager where clients can obtain device authorization code
|
||||||
|
DeviceAuthEndpoint string
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateURL validates input http url
|
// validateURL validates input http url
|
||||||
|
|||||||
52
management/server/error.go
Normal file
52
management/server/error.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UserAlreadyExists indicates that user already exists
|
||||||
|
UserAlreadyExists ErrorType = 1
|
||||||
|
// AccountNotFound indicates that specified account hasn't been found
|
||||||
|
AccountNotFound ErrorType = iota
|
||||||
|
// PreconditionFailed indicates that some pre-condition for the operation hasn't been fulfilled
|
||||||
|
PreconditionFailed ErrorType = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorType is a type of the Error
|
||||||
|
type ErrorType int32
|
||||||
|
|
||||||
|
// Error is an internal error
|
||||||
|
type Error struct {
|
||||||
|
errorType ErrorType
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the Type of the error
|
||||||
|
func (e *Error) Type() ErrorType {
|
||||||
|
return e.errorType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error is an error string
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return e.message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf returns Error(errorType, fmt.Sprintf(format, a...)).
|
||||||
|
func Errorf(errorType ErrorType, format string, a ...interface{}) error {
|
||||||
|
return &Error{
|
||||||
|
errorType: errorType,
|
||||||
|
message: fmt.Sprintf(format, a...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromError returns Error, true if the provided error is of type of Error. nil, false otherwise
|
||||||
|
func FromError(err error) (s *Error, ok bool) {
|
||||||
|
if err == nil {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
return e, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -25,6 +27,9 @@ type FileStore struct {
|
|||||||
PrivateDomain2AccountId map[string]string `json:"-"`
|
PrivateDomain2AccountId map[string]string `json:"-"`
|
||||||
PeerKeyId2SrcRulesId map[string]map[string]struct{} `json:"-"`
|
PeerKeyId2SrcRulesId map[string]map[string]struct{} `json:"-"`
|
||||||
PeerKeyId2DstRulesId map[string]map[string]struct{} `json:"-"`
|
PeerKeyId2DstRulesId map[string]map[string]struct{} `json:"-"`
|
||||||
|
PeerKeyID2RouteIDs map[string]map[string]struct{} `json:"-"`
|
||||||
|
AccountPrefix2RouteIDs map[string]map[string][]string `json:"-"`
|
||||||
|
InstallationID string
|
||||||
|
|
||||||
// mutex to synchronise Store read/write operations
|
// mutex to synchronise Store read/write operations
|
||||||
mux sync.Mutex `json:"-"`
|
mux sync.Mutex `json:"-"`
|
||||||
@@ -51,7 +56,9 @@ func restore(file string) (*FileStore, error) {
|
|||||||
UserId2AccountId: make(map[string]string),
|
UserId2AccountId: make(map[string]string),
|
||||||
PrivateDomain2AccountId: make(map[string]string),
|
PrivateDomain2AccountId: make(map[string]string),
|
||||||
PeerKeyId2SrcRulesId: make(map[string]map[string]struct{}),
|
PeerKeyId2SrcRulesId: make(map[string]map[string]struct{}),
|
||||||
|
PeerKeyID2RouteIDs: make(map[string]map[string]struct{}),
|
||||||
PeerKeyId2DstRulesId: make(map[string]map[string]struct{}),
|
PeerKeyId2DstRulesId: make(map[string]map[string]struct{}),
|
||||||
|
AccountPrefix2RouteIDs: make(map[string]map[string][]string),
|
||||||
storeFile: file,
|
storeFile: file,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,8 +81,10 @@ func restore(file string) (*FileStore, error) {
|
|||||||
store.PeerKeyId2AccountId = make(map[string]string)
|
store.PeerKeyId2AccountId = make(map[string]string)
|
||||||
store.UserId2AccountId = make(map[string]string)
|
store.UserId2AccountId = make(map[string]string)
|
||||||
store.PrivateDomain2AccountId = make(map[string]string)
|
store.PrivateDomain2AccountId = make(map[string]string)
|
||||||
store.PeerKeyId2SrcRulesId = map[string]map[string]struct{}{}
|
store.PeerKeyId2SrcRulesId = make(map[string]map[string]struct{})
|
||||||
store.PeerKeyId2DstRulesId = map[string]map[string]struct{}{}
|
store.PeerKeyId2DstRulesId = make(map[string]map[string]struct{})
|
||||||
|
store.PeerKeyID2RouteIDs = make(map[string]map[string]struct{})
|
||||||
|
store.AccountPrefix2RouteIDs = make(map[string]map[string][]string)
|
||||||
|
|
||||||
for accountId, account := range store.Accounts {
|
for accountId, account := range store.Accounts {
|
||||||
for setupKeyId := range account.SetupKeys {
|
for setupKeyId := range account.SetupKeys {
|
||||||
@@ -116,6 +125,25 @@ func restore(file string) (*FileStore, error) {
|
|||||||
for _, user := range account.Users {
|
for _, user := range account.Users {
|
||||||
store.UserId2AccountId[user.Id] = accountId
|
store.UserId2AccountId[user.Id] = accountId
|
||||||
}
|
}
|
||||||
|
for _, route := range account.Routes {
|
||||||
|
if route.Peer == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if store.PeerKeyID2RouteIDs[route.Peer] == nil {
|
||||||
|
store.PeerKeyID2RouteIDs[route.Peer] = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
store.PeerKeyID2RouteIDs[route.Peer][route.ID] = struct{}{}
|
||||||
|
if store.AccountPrefix2RouteIDs[account.Id] == nil {
|
||||||
|
store.AccountPrefix2RouteIDs[account.Id] = make(map[string][]string)
|
||||||
|
}
|
||||||
|
if _, ok := store.AccountPrefix2RouteIDs[account.Id][route.Network.String()]; !ok {
|
||||||
|
store.AccountPrefix2RouteIDs[account.Id][route.Network.String()] = make([]string, 0)
|
||||||
|
}
|
||||||
|
store.AccountPrefix2RouteIDs[account.Id][route.Network.String()] = append(
|
||||||
|
store.AccountPrefix2RouteIDs[account.Id][route.Network.String()],
|
||||||
|
route.ID,
|
||||||
|
)
|
||||||
|
}
|
||||||
if account.Domain != "" && account.DomainCategory == PrivateCategory &&
|
if account.Domain != "" && account.DomainCategory == PrivateCategory &&
|
||||||
account.IsDomainPrimaryAccount {
|
account.IsDomainPrimaryAccount {
|
||||||
store.PrivateDomain2AccountId[account.Domain] = accountId
|
store.PrivateDomain2AccountId[account.Domain] = accountId
|
||||||
@@ -177,11 +205,12 @@ func (s *FileStore) DeletePeer(accountId string, peerKey string) (*Peer, error)
|
|||||||
if peer == nil {
|
if peer == nil {
|
||||||
return nil, status.Errorf(codes.NotFound, "peer not found")
|
return nil, status.Errorf(codes.NotFound, "peer not found")
|
||||||
}
|
}
|
||||||
|
peerRoutes := s.PeerKeyID2RouteIDs[peerKey]
|
||||||
delete(account.Peers, peerKey)
|
delete(account.Peers, peerKey)
|
||||||
delete(s.PeerKeyId2AccountId, peerKey)
|
delete(s.PeerKeyId2AccountId, peerKey)
|
||||||
delete(s.PeerKeyId2DstRulesId, peerKey)
|
delete(s.PeerKeyId2DstRulesId, peerKey)
|
||||||
delete(s.PeerKeyId2SrcRulesId, peerKey)
|
delete(s.PeerKeyId2SrcRulesId, peerKey)
|
||||||
|
delete(s.PeerKeyID2RouteIDs, peerKey)
|
||||||
|
|
||||||
// cleanup groups
|
// cleanup groups
|
||||||
for _, g := range account.Groups {
|
for _, g := range account.Groups {
|
||||||
@@ -194,6 +223,11 @@ func (s *FileStore) DeletePeer(accountId string, peerKey string) (*Peer, error)
|
|||||||
g.Peers = peers
|
g.Peers = peers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for routeID := range peerRoutes {
|
||||||
|
account.Routes[routeID].Enabled = false
|
||||||
|
account.Routes[routeID].Peer = ""
|
||||||
|
}
|
||||||
|
|
||||||
err = s.persist(s.storeFile)
|
err = s.persist(s.storeFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -238,10 +272,14 @@ func (s *FileStore) SaveAccount(account *Account) error {
|
|||||||
s.SetupKeyId2AccountId[strings.ToUpper(keyId)] = account.Id
|
s.SetupKeyId2AccountId[strings.ToUpper(keyId)] = account.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enforce peer to account index and delete peer to route indexes for rebuild
|
||||||
for _, peer := range account.Peers {
|
for _, peer := range account.Peers {
|
||||||
s.PeerKeyId2AccountId[peer.Key] = account.Id
|
s.PeerKeyId2AccountId[peer.Key] = account.Id
|
||||||
|
delete(s.PeerKeyID2RouteIDs, peer.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(s.AccountPrefix2RouteIDs, account.Id)
|
||||||
|
|
||||||
// remove all peers related to account from rules indexes
|
// remove all peers related to account from rules indexes
|
||||||
cleanIDs := make([]string, 0)
|
cleanIDs := make([]string, 0)
|
||||||
for key := range s.PeerKeyId2SrcRulesId {
|
for key := range s.PeerKeyId2SrcRulesId {
|
||||||
@@ -294,6 +332,26 @@ func (s *FileStore) SaveAccount(account *Account) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, route := range account.Routes {
|
||||||
|
if route.Peer == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.PeerKeyID2RouteIDs[route.Peer] == nil {
|
||||||
|
s.PeerKeyID2RouteIDs[route.Peer] = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
s.PeerKeyID2RouteIDs[route.Peer][route.ID] = struct{}{}
|
||||||
|
if s.AccountPrefix2RouteIDs[account.Id] == nil {
|
||||||
|
s.AccountPrefix2RouteIDs[account.Id] = make(map[string][]string)
|
||||||
|
}
|
||||||
|
if _, ok := s.AccountPrefix2RouteIDs[account.Id][route.Network.String()]; !ok {
|
||||||
|
s.AccountPrefix2RouteIDs[account.Id][route.Network.String()] = make([]string, 0)
|
||||||
|
}
|
||||||
|
s.AccountPrefix2RouteIDs[account.Id][route.Network.String()] = append(
|
||||||
|
s.AccountPrefix2RouteIDs[account.Id][route.Network.String()],
|
||||||
|
route.ID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
for _, user := range account.Users {
|
for _, user := range account.Users {
|
||||||
s.UserId2AccountId[user.Id] = account.Id
|
s.UserId2AccountId[user.Id] = account.Id
|
||||||
}
|
}
|
||||||
@@ -305,6 +363,7 @@ func (s *FileStore) SaveAccount(account *Account) error {
|
|||||||
return s.persist(s.storeFile)
|
return s.persist(s.storeFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAccountByPrivateDomain returns account by private domain
|
||||||
func (s *FileStore) GetAccountByPrivateDomain(domain string) (*Account, error) {
|
func (s *FileStore) GetAccountByPrivateDomain(domain string) (*Account, error) {
|
||||||
accountId, accountIdFound := s.PrivateDomain2AccountId[strings.ToLower(domain)]
|
accountId, accountIdFound := s.PrivateDomain2AccountId[strings.ToLower(domain)]
|
||||||
if !accountIdFound {
|
if !accountIdFound {
|
||||||
@@ -322,6 +381,7 @@ func (s *FileStore) GetAccountByPrivateDomain(domain string) (*Account, error) {
|
|||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAccountBySetupKey returns account by setup key id
|
||||||
func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
||||||
accountId, accountIdFound := s.SetupKeyId2AccountId[strings.ToUpper(setupKey)]
|
accountId, accountIdFound := s.SetupKeyId2AccountId[strings.ToUpper(setupKey)]
|
||||||
if !accountIdFound {
|
if !accountIdFound {
|
||||||
@@ -336,6 +396,7 @@ func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
|||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAccountPeers returns account peers
|
||||||
func (s *FileStore) GetAccountPeers(accountId string) ([]*Peer, error) {
|
func (s *FileStore) GetAccountPeers(accountId string) ([]*Peer, error) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
@@ -353,14 +414,18 @@ func (s *FileStore) GetAccountPeers(accountId string) ([]*Peer, error) {
|
|||||||
return peers, nil
|
return peers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllAccounts returns all accounts
|
||||||
func (s *FileStore) GetAllAccounts() (all []*Account) {
|
func (s *FileStore) GetAllAccounts() (all []*Account) {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
for _, a := range s.Accounts {
|
for _, a := range s.Accounts {
|
||||||
all = append(all, a)
|
all = append(all, a.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
return all
|
return all
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAccount returns an account for id
|
||||||
func (s *FileStore) GetAccount(accountId string) (*Account, error) {
|
func (s *FileStore) GetAccount(accountId string) (*Account, error) {
|
||||||
account, accountFound := s.Accounts[accountId]
|
account, accountFound := s.Accounts[accountId]
|
||||||
if !accountFound {
|
if !accountFound {
|
||||||
@@ -370,6 +435,7 @@ func (s *FileStore) GetAccount(accountId string) (*Account, error) {
|
|||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserAccount returns a user account
|
||||||
func (s *FileStore) GetUserAccount(userId string) (*Account, error) {
|
func (s *FileStore) GetUserAccount(userId string) (*Account, error) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
@@ -382,10 +448,7 @@ func (s *FileStore) GetUserAccount(userId string) (*Account, error) {
|
|||||||
return s.GetAccount(accountId)
|
return s.GetAccount(accountId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileStore) GetPeerAccount(peerKey string) (*Account, error) {
|
func (s *FileStore) getPeerAccount(peerKey string) (*Account, error) {
|
||||||
s.mux.Lock()
|
|
||||||
defer s.mux.Unlock()
|
|
||||||
|
|
||||||
accountId, accountIdFound := s.PeerKeyId2AccountId[peerKey]
|
accountId, accountIdFound := s.PeerKeyId2AccountId[peerKey]
|
||||||
if !accountIdFound {
|
if !accountIdFound {
|
||||||
return nil, status.Errorf(codes.NotFound, "Provided peer key doesn't exists %s", peerKey)
|
return nil, status.Errorf(codes.NotFound, "Provided peer key doesn't exists %s", peerKey)
|
||||||
@@ -394,6 +457,15 @@ func (s *FileStore) GetPeerAccount(peerKey string) (*Account, error) {
|
|||||||
return s.GetAccount(accountId)
|
return s.GetAccount(accountId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPeerAccount returns user account if exists
|
||||||
|
func (s *FileStore) GetPeerAccount(peerKey string) (*Account, error) {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
return s.getPeerAccount(peerKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeerSrcRules return list of source rules for peer
|
||||||
func (s *FileStore) GetPeerSrcRules(accountId, peerKey string) ([]*Rule, error) {
|
func (s *FileStore) GetPeerSrcRules(accountId, peerKey string) ([]*Rule, error) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
@@ -419,6 +491,7 @@ func (s *FileStore) GetPeerSrcRules(accountId, peerKey string) ([]*Rule, error)
|
|||||||
return rules, nil
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPeerDstRules return list of destination rules for peer
|
||||||
func (s *FileStore) GetPeerDstRules(accountId, peerKey string) ([]*Rule, error) {
|
func (s *FileStore) GetPeerDstRules(accountId, peerKey string) ([]*Rule, error) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
@@ -443,3 +516,71 @@ func (s *FileStore) GetPeerDstRules(accountId, peerKey string) ([]*Rule, error)
|
|||||||
|
|
||||||
return rules, nil
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPeerRoutes return list of routes for peer
|
||||||
|
func (s *FileStore) GetPeerRoutes(peerKey string) ([]*route.Route, error) {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
account, err := s.getPeerAccount(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var routes []*route.Route
|
||||||
|
|
||||||
|
routeIDs, ok := s.PeerKeyID2RouteIDs[peerKey]
|
||||||
|
if !ok {
|
||||||
|
return routes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for id := range routeIDs {
|
||||||
|
route, found := account.Routes[id]
|
||||||
|
if found {
|
||||||
|
routes = append(routes, route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoutesByPrefix return list of routes by account and route prefix
|
||||||
|
func (s *FileStore) GetRoutesByPrefix(accountID string, prefix netip.Prefix) ([]*route.Route, error) {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
account, err := s.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
routeIDs, ok := s.AccountPrefix2RouteIDs[accountID][prefix.String()]
|
||||||
|
if !ok {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "no routes for prefix: %v", prefix.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var routes []*route.Route
|
||||||
|
for _, id := range routeIDs {
|
||||||
|
route, found := account.Routes[id]
|
||||||
|
if found {
|
||||||
|
routes = append(routes, route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInstallationID returns the installation ID from the store
|
||||||
|
func (s *FileStore) GetInstallationID() string {
|
||||||
|
return s.InstallationID
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveInstallationID saves the installation ID
|
||||||
|
func (s *FileStore) SaveInstallationID(id string) error {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
s.InstallationID = id
|
||||||
|
|
||||||
|
return s.persist(s.storeFile)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
gPeer "google.golang.org/grpc/peer"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -15,11 +17,12 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
|
gRPCPeer "google.golang.org/grpc/peer"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server an instance of a Management server
|
// GRPCServer an instance of a Management gRPC API server
|
||||||
type Server struct {
|
type GRPCServer struct {
|
||||||
accountManager AccountManager
|
accountManager AccountManager
|
||||||
wgKey wgtypes.Key
|
wgKey wgtypes.Key
|
||||||
proto.UnimplementedManagementServiceServer
|
proto.UnimplementedManagementServiceServer
|
||||||
@@ -30,7 +33,7 @@ type Server struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates a new Management server
|
// NewServer creates a new Management server
|
||||||
func NewServer(config *Config, accountManager AccountManager, peersUpdateManager *PeersUpdateManager, turnCredentialsManager TURNCredentialsManager) (*Server, error) {
|
func NewServer(config *Config, accountManager AccountManager, peersUpdateManager *PeersUpdateManager, turnCredentialsManager TURNCredentialsManager) (*GRPCServer, error) {
|
||||||
key, err := wgtypes.GeneratePrivateKey()
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -50,7 +53,7 @@ func NewServer(config *Config, accountManager AccountManager, peersUpdateManager
|
|||||||
log.Debug("unable to use http config to create new jwt middleware")
|
log.Debug("unable to use http config to create new jwt middleware")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Server{
|
return &GRPCServer{
|
||||||
wgKey: key,
|
wgKey: key,
|
||||||
// peerKey -> event channel
|
// peerKey -> event channel
|
||||||
peersUpdateManager: peersUpdateManager,
|
peersUpdateManager: peersUpdateManager,
|
||||||
@@ -61,7 +64,7 @@ func NewServer(config *Config, accountManager AccountManager, peersUpdateManager
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetServerKey(ctx context.Context, req *proto.Empty) (*proto.ServerKeyResponse, error) {
|
func (s *GRPCServer) GetServerKey(ctx context.Context, req *proto.Empty) (*proto.ServerKeyResponse, error) {
|
||||||
// todo introduce something more meaningful with the key expiration/rotation
|
// todo introduce something more meaningful with the key expiration/rotation
|
||||||
now := time.Now().Add(24 * time.Hour)
|
now := time.Now().Add(24 * time.Hour)
|
||||||
secs := int64(now.Second())
|
secs := int64(now.Second())
|
||||||
@@ -76,8 +79,11 @@ func (s *Server) GetServerKey(ctx context.Context, req *proto.Empty) (*proto.Ser
|
|||||||
|
|
||||||
// Sync validates the existence of a connecting peer, sends an initial state (all available for the connecting peers) and
|
// Sync validates the existence of a connecting peer, sends an initial state (all available for the connecting peers) and
|
||||||
// notifies the connected peer of any updates (e.g. new peers under the same account)
|
// notifies the connected peer of any updates (e.g. new peers under the same account)
|
||||||
func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_SyncServer) error {
|
func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_SyncServer) error {
|
||||||
log.Debugf("Sync request from peer %s", req.WgPubKey)
|
p, ok := gRPCPeer.FromContext(srv.Context())
|
||||||
|
if ok {
|
||||||
|
log.Debugf("Sync request from peer [%s] [%s]", req.WgPubKey, p.Addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -87,17 +93,24 @@ func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_S
|
|||||||
|
|
||||||
peer, err := s.accountManager.GetPeer(peerKey.String())
|
peer, err := s.accountManager.GetPeer(peerKey.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.PermissionDenied, "provided peer with the key wgPubKey %s is not registered", peerKey.String())
|
p, _ := gPeer.FromContext(srv.Context())
|
||||||
|
msg := status.Errorf(codes.PermissionDenied, "provided peer with the key wgPubKey %s is not registered, remote addr is %s", peerKey.String(), p.Addr.String())
|
||||||
|
log.Debug(msg)
|
||||||
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
syncReq := &proto.SyncRequest{}
|
syncReq := &proto.SyncRequest{}
|
||||||
err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, syncReq)
|
err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, syncReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.InvalidArgument, "invalid request message")
|
p, _ := gPeer.FromContext(srv.Context())
|
||||||
|
msg := status.Errorf(codes.InvalidArgument, "invalid request message from %s,remote addr is %s", peerKey.String(), p.Addr.String())
|
||||||
|
log.Debug(msg)
|
||||||
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.sendInitialSync(peerKey, peer, srv)
|
err = s.sendInitialSync(peerKey, peer, srv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Debugf("error while sending initial sync for %s: %v", peerKey.String(), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +129,7 @@ func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_S
|
|||||||
// condition when there are some updates
|
// condition when there are some updates
|
||||||
case update, open := <-updates:
|
case update, open := <-updates:
|
||||||
if !open {
|
if !open {
|
||||||
// updates channel has been closed
|
log.Debugf("updates channel for peer %s was closed", peerKey.String())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Debugf("recevied an update for peer %s", peerKey.String())
|
log.Debugf("recevied an update for peer %s", peerKey.String())
|
||||||
@@ -150,7 +163,7 @@ func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Peer, error) {
|
func (s *GRPCServer) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Peer, error) {
|
||||||
var (
|
var (
|
||||||
reqSetupKey string
|
reqSetupKey string
|
||||||
userId string
|
userId string
|
||||||
@@ -168,7 +181,7 @@ func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Pe
|
|||||||
return nil, status.Errorf(codes.Internal, "invalid jwt token, err: %v", err)
|
return nil, status.Errorf(codes.Internal, "invalid jwt token, err: %v", err)
|
||||||
}
|
}
|
||||||
claims := jwtclaims.ExtractClaimsWithToken(token, s.config.HttpConfig.AuthAudience)
|
claims := jwtclaims.ExtractClaimsWithToken(token, s.config.HttpConfig.AuthAudience)
|
||||||
_, err = s.accountManager.GetAccountWithAuthorizationClaims(claims)
|
_, err = s.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "unable to fetch account with claims, err: %v", err)
|
return nil, status.Errorf(codes.Internal, "unable to fetch account with claims, err: %v", err)
|
||||||
}
|
}
|
||||||
@@ -230,7 +243,7 @@ func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Pe
|
|||||||
peersToSend = append(peersToSend, p)
|
peersToSend = append(peersToSend, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update := toSyncResponse(s.config, remotePeer, peersToSend, nil, networkMap.Network.CurrentSerial(), networkMap.Network)
|
update := toSyncResponse(s.config, remotePeer, peersToSend, networkMap.Routes, nil, networkMap.Network.CurrentSerial(), networkMap.Network)
|
||||||
err = s.peersUpdateManager.SendUpdate(remotePeer.Key, &UpdateMessage{Update: update})
|
err = s.peersUpdateManager.SendUpdate(remotePeer.Key, &UpdateMessage{Update: update})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// todo rethink if we should keep this return
|
// todo rethink if we should keep this return
|
||||||
@@ -245,8 +258,11 @@ func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Pe
|
|||||||
// In case it is, the login is successful
|
// In case it is, the login is successful
|
||||||
// In case it isn't, the endpoint checks whether setup key is provided within the request and tries to register a peer.
|
// In case it isn't, the endpoint checks whether setup key is provided within the request and tries to register a peer.
|
||||||
// In case of the successful registration login is also successful
|
// In case of the successful registration login is also successful
|
||||||
func (s *Server) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||||
log.Debugf("Login request from peer %s", req.WgPubKey)
|
p, ok := gRPCPeer.FromContext(ctx)
|
||||||
|
if ok {
|
||||||
|
log.Debugf("Login request from peer [%s] [%s]", req.WgPubKey, p.Addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -265,8 +281,13 @@ func (s *Server) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto
|
|||||||
if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.NotFound {
|
if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.NotFound {
|
||||||
// peer doesn't exist -> check if setup key was provided
|
// peer doesn't exist -> check if setup key was provided
|
||||||
if loginReq.GetJwtToken() == "" && loginReq.GetSetupKey() == "" {
|
if loginReq.GetJwtToken() == "" && loginReq.GetSetupKey() == "" {
|
||||||
// absent setup key -> permission denied
|
// absent setup key or jwt -> permission denied
|
||||||
return nil, status.Errorf(codes.PermissionDenied, "provided peer with the key wgPubKey %s is not registered and no setup key or jwt was provided", peerKey.String())
|
p, _ := gPeer.FromContext(ctx)
|
||||||
|
msg := status.Errorf(codes.PermissionDenied,
|
||||||
|
"provided peer with the key wgPubKey %s is not registered and no setup key or jwt was provided,"+
|
||||||
|
" remote addr is %s", peerKey.String(), p.Addr.String())
|
||||||
|
log.Debug(msg)
|
||||||
|
return nil, msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup key or jwt is present -> try normal registration flow
|
// setup key or jwt is present -> try normal registration flow
|
||||||
@@ -407,13 +428,15 @@ func toRemotePeerConfig(peers []*Peer) []*proto.RemotePeerConfig {
|
|||||||
return remotePeers
|
return remotePeers
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSyncResponse(config *Config, peer *Peer, peers []*Peer, turnCredentials *TURNCredentials, serial uint64, network *Network) *proto.SyncResponse {
|
func toSyncResponse(config *Config, peer *Peer, peers []*Peer, routes []*route.Route, turnCredentials *TURNCredentials, serial uint64, network *Network) *proto.SyncResponse {
|
||||||
wtConfig := toWiretrusteeConfig(config, turnCredentials)
|
wtConfig := toWiretrusteeConfig(config, turnCredentials)
|
||||||
|
|
||||||
pConfig := toPeerConfig(peer, network)
|
pConfig := toPeerConfig(peer, network)
|
||||||
|
|
||||||
remotePeers := toRemotePeerConfig(peers)
|
remotePeers := toRemotePeerConfig(peers)
|
||||||
|
|
||||||
|
routesUpdate := toProtocolRoutes(routes)
|
||||||
|
|
||||||
return &proto.SyncResponse{
|
return &proto.SyncResponse{
|
||||||
WiretrusteeConfig: wtConfig,
|
WiretrusteeConfig: wtConfig,
|
||||||
PeerConfig: pConfig,
|
PeerConfig: pConfig,
|
||||||
@@ -424,17 +447,18 @@ func toSyncResponse(config *Config, peer *Peer, peers []*Peer, turnCredentials *
|
|||||||
PeerConfig: pConfig,
|
PeerConfig: pConfig,
|
||||||
RemotePeers: remotePeers,
|
RemotePeers: remotePeers,
|
||||||
RemotePeersIsEmpty: len(remotePeers) == 0,
|
RemotePeersIsEmpty: len(remotePeers) == 0,
|
||||||
|
Routes: routesUpdate,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsHealthy indicates whether the service is healthy
|
// IsHealthy indicates whether the service is healthy
|
||||||
func (s *Server) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Empty, error) {
|
func (s *GRPCServer) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Empty, error) {
|
||||||
return &proto.Empty{}, nil
|
return &proto.Empty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization
|
// sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization
|
||||||
func (s *Server) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.ManagementService_SyncServer) error {
|
func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.ManagementService_SyncServer) error {
|
||||||
networkMap, err := s.accountManager.GetNetworkMap(peer.Key)
|
networkMap, err := s.accountManager.GetNetworkMap(peer.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("error getting a list of peers for a peer %s", peer.Key)
|
log.Warnf("error getting a list of peers for a peer %s", peer.Key)
|
||||||
@@ -449,7 +473,7 @@ func (s *Server) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.Mana
|
|||||||
} else {
|
} else {
|
||||||
turnCredentials = nil
|
turnCredentials = nil
|
||||||
}
|
}
|
||||||
plainResp := toSyncResponse(s.config, peer, networkMap.Peers, turnCredentials, networkMap.Network.CurrentSerial(), networkMap.Network)
|
plainResp := toSyncResponse(s.config, peer, networkMap.Peers, networkMap.Routes, turnCredentials, networkMap.Network.CurrentSerial(), networkMap.Network)
|
||||||
|
|
||||||
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
|
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -472,7 +496,7 @@ func (s *Server) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.Mana
|
|||||||
// GetDeviceAuthorizationFlow returns a device authorization flow information
|
// GetDeviceAuthorizationFlow returns a device authorization flow information
|
||||||
// This is used for initiating an Oauth 2 device authorization grant flow
|
// This is used for initiating an Oauth 2 device authorization grant flow
|
||||||
// which will be used by our clients to Login
|
// which will be used by our clients to Login
|
||||||
func (s *Server) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
func (s *GRPCServer) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||||
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMSG := fmt.Sprintf("error while parsing peer's Wireguard public key %s on GetDeviceAuthorizationFlow request.", req.WgPubKey)
|
errMSG := fmt.Sprintf("error while parsing peer's Wireguard public key %s on GetDeviceAuthorizationFlow request.", req.WgPubKey)
|
||||||
@@ -487,7 +511,7 @@ func (s *Server) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.Encr
|
|||||||
return nil, status.Error(codes.InvalidArgument, errMSG)
|
return nil, status.Error(codes.InvalidArgument, errMSG)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.config.DeviceAuthorizationFlow == nil {
|
if s.config.DeviceAuthorizationFlow == nil || s.config.DeviceAuthorizationFlow.Provider == string(NONE) {
|
||||||
return nil, status.Error(codes.NotFound, "no device authorization flow information available")
|
return nil, status.Error(codes.NotFound, "no device authorization flow information available")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,10 +523,12 @@ func (s *Server) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.Encr
|
|||||||
flowInfoResp := &proto.DeviceAuthorizationFlow{
|
flowInfoResp := &proto.DeviceAuthorizationFlow{
|
||||||
Provider: proto.DeviceAuthorizationFlowProvider(provider),
|
Provider: proto.DeviceAuthorizationFlowProvider(provider),
|
||||||
ProviderConfig: &proto.ProviderConfig{
|
ProviderConfig: &proto.ProviderConfig{
|
||||||
ClientID: s.config.DeviceAuthorizationFlow.ProviderConfig.ClientID,
|
ClientID: s.config.DeviceAuthorizationFlow.ProviderConfig.ClientID,
|
||||||
ClientSecret: s.config.DeviceAuthorizationFlow.ProviderConfig.ClientSecret,
|
ClientSecret: s.config.DeviceAuthorizationFlow.ProviderConfig.ClientSecret,
|
||||||
Domain: s.config.DeviceAuthorizationFlow.ProviderConfig.Domain,
|
Domain: s.config.DeviceAuthorizationFlow.ProviderConfig.Domain,
|
||||||
Audience: s.config.DeviceAuthorizationFlow.ProviderConfig.Audience,
|
Audience: s.config.DeviceAuthorizationFlow.ProviderConfig.Audience,
|
||||||
|
DeviceAuthEndpoint: s.config.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint,
|
||||||
|
TokenEndpoint: s.config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,3 +3,5 @@ generate:
|
|||||||
models: true
|
models: true
|
||||||
embedded-spec: false
|
embedded-spec: false
|
||||||
output: types.gen.go
|
output: types.gen.go
|
||||||
|
compatibility:
|
||||||
|
always-prefix-enum-values: true
|
||||||
@@ -11,6 +11,6 @@ fi
|
|||||||
old_pwd=$(pwd)
|
old_pwd=$(pwd)
|
||||||
script_path=$(dirname $(realpath "$0"))
|
script_path=$(dirname $(realpath "$0"))
|
||||||
cd "$script_path"
|
cd "$script_path"
|
||||||
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.11.0
|
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@4a1477f6a8ba6ca8115cc23bb2fb67f0b9fca18e
|
||||||
oapi-codegen --config cfg.yaml openapi.yml
|
oapi-codegen --config cfg.yaml openapi.yml
|
||||||
cd "$old_pwd"
|
cd "$old_pwd"
|
||||||
@@ -14,6 +14,10 @@ tags:
|
|||||||
description: Interact with and view information about groups.
|
description: Interact with and view information about groups.
|
||||||
- name: Rules
|
- name: Rules
|
||||||
description: Interact with and view information about rules.
|
description: Interact with and view information about rules.
|
||||||
|
- name: Routes
|
||||||
|
description: Interact with and view information about routes.
|
||||||
|
- name: DNS
|
||||||
|
description: Interact with and view information about DNS configuration.
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
User:
|
User:
|
||||||
@@ -29,13 +33,59 @@ components:
|
|||||||
description: User's name from idp provider
|
description: User's name from idp provider
|
||||||
type: string
|
type: string
|
||||||
role:
|
role:
|
||||||
description: User's Netbird account role
|
description: User's NetBird account role
|
||||||
type: string
|
type: string
|
||||||
|
status:
|
||||||
|
description: User's status
|
||||||
|
type: string
|
||||||
|
enum: [ "active","invited","disabled" ]
|
||||||
|
auto_groups:
|
||||||
|
description: Groups to auto-assign to peers registered by this user
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- email
|
- email
|
||||||
- name
|
- name
|
||||||
- role
|
- role
|
||||||
|
- auto_groups
|
||||||
|
- status
|
||||||
|
UserRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
role:
|
||||||
|
description: User's NetBird account role
|
||||||
|
type: string
|
||||||
|
auto_groups:
|
||||||
|
description: Groups to auto-assign to peers registered by this user
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- role
|
||||||
|
- auto_groups
|
||||||
|
UserCreateRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
role:
|
||||||
|
description: User's NetBird account role
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
description: User's Email to send invite to
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: User's full name
|
||||||
|
type: string
|
||||||
|
auto_groups:
|
||||||
|
description: Groups to auto-assign to peers registered by this user
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- role
|
||||||
|
- auto_groups
|
||||||
|
- email
|
||||||
PeerMinimum:
|
PeerMinimum:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -74,20 +124,18 @@ components:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/GroupMinimum'
|
$ref: '#/components/schemas/GroupMinimum'
|
||||||
activated_by:
|
|
||||||
description: Provides information of who activated the Peer. User or Setup Key
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
value:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- type
|
|
||||||
- value
|
|
||||||
ssh_enabled:
|
ssh_enabled:
|
||||||
description: Indicates whether SSH server is enabled on this peer
|
description: Indicates whether SSH server is enabled on this peer
|
||||||
type: boolean
|
type: boolean
|
||||||
|
user_id:
|
||||||
|
description: User ID of the user that enrolled this peer
|
||||||
|
type: string
|
||||||
|
hostname:
|
||||||
|
description: Hostname of the machine
|
||||||
|
type: string
|
||||||
|
ui_version:
|
||||||
|
description: Peer's desktop UI version
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- ip
|
- ip
|
||||||
- connected
|
- connected
|
||||||
@@ -95,8 +143,8 @@ components:
|
|||||||
- os
|
- os
|
||||||
- version
|
- version
|
||||||
- groups
|
- groups
|
||||||
- activated_by
|
|
||||||
- ssh_enabled
|
- ssh_enabled
|
||||||
|
- hostname
|
||||||
SetupKey:
|
SetupKey:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -132,6 +180,15 @@ components:
|
|||||||
state:
|
state:
|
||||||
description: Setup key status, "valid", "overused","expired" or "revoked"
|
description: Setup key status, "valid", "overused","expired" or "revoked"
|
||||||
type: string
|
type: string
|
||||||
|
auto_groups:
|
||||||
|
description: Setup key groups to auto-assign to peers registered with this key
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
updated_at:
|
||||||
|
description: Setup key last update date
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- key
|
- key
|
||||||
@@ -143,6 +200,8 @@ components:
|
|||||||
- used_times
|
- used_times
|
||||||
- last_used
|
- last_used
|
||||||
- state
|
- state
|
||||||
|
- auto_groups
|
||||||
|
- updated_at
|
||||||
SetupKeyRequest:
|
SetupKeyRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -158,11 +217,17 @@ components:
|
|||||||
revoked:
|
revoked:
|
||||||
description: Setup key revocation status
|
description: Setup key revocation status
|
||||||
type: boolean
|
type: boolean
|
||||||
|
auto_groups:
|
||||||
|
description: Setup key groups to auto-assign to peers registered with this key
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- type
|
- type
|
||||||
- expires_in
|
- expires_in
|
||||||
- revoked
|
- revoked
|
||||||
|
- auto_groups
|
||||||
GroupMinimum:
|
GroupMinimum:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -191,17 +256,13 @@ components:
|
|||||||
$ref: '#/components/schemas/PeerMinimum'
|
$ref: '#/components/schemas/PeerMinimum'
|
||||||
required:
|
required:
|
||||||
- peers
|
- peers
|
||||||
GroupPatchOperation:
|
PatchMinimum:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
op:
|
op:
|
||||||
description: Patch operation type
|
description: Patch operation type
|
||||||
type: string
|
type: string
|
||||||
enum: [ "replace","add","remove" ]
|
enum: [ "replace","add","remove" ]
|
||||||
path:
|
|
||||||
description: Group field to update in form /<field>
|
|
||||||
type: string
|
|
||||||
enum: [ "name","peers" ]
|
|
||||||
value:
|
value:
|
||||||
description: Values to be applied
|
description: Values to be applied
|
||||||
type: array
|
type: array
|
||||||
@@ -209,8 +270,19 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- op
|
- op
|
||||||
- path
|
|
||||||
- value
|
- value
|
||||||
|
GroupPatchOperation:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PatchMinimum'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
description: Group field to update in form /<field>
|
||||||
|
type: string
|
||||||
|
enum: [ "name","peers" ]
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
|
||||||
RuleMinimum:
|
RuleMinimum:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -257,25 +329,149 @@ components:
|
|||||||
- sources
|
- sources
|
||||||
- destinations
|
- destinations
|
||||||
RulePatchOperation:
|
RulePatchOperation:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PatchMinimum'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
description: Rule field to update in form /<field>
|
||||||
|
type: string
|
||||||
|
enum: [ "name","description","disabled","flow","sources","destinations" ]
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
RouteRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
op:
|
description:
|
||||||
description: Patch operation type
|
description: Route description
|
||||||
type: string
|
type: string
|
||||||
enum: [ "replace","add","remove" ]
|
network_id:
|
||||||
path:
|
description: Route network identifier, to group HA routes
|
||||||
description: Rule field to update in form /<field>
|
|
||||||
type: string
|
type: string
|
||||||
enum: [ "name","description","disabled","flow","sources","destinations" ]
|
maxLength: 40
|
||||||
value:
|
minLength: 1
|
||||||
description: Values to be applied
|
enabled:
|
||||||
|
description: Route status
|
||||||
|
type: boolean
|
||||||
|
peer:
|
||||||
|
description: Peer Identifier associated with route
|
||||||
|
type: string
|
||||||
|
network:
|
||||||
|
description: Network range in CIDR format
|
||||||
|
type: string
|
||||||
|
metric:
|
||||||
|
description: Route metric number. Lowest number has higher priority
|
||||||
|
type: integer
|
||||||
|
maximum: 9999
|
||||||
|
minimum: 1
|
||||||
|
masquerade:
|
||||||
|
description: Indicate if peer should masquerade traffic to this route's prefix
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- description
|
||||||
|
- network_id
|
||||||
|
- enabled
|
||||||
|
- peer
|
||||||
|
- network
|
||||||
|
- metric
|
||||||
|
- masquerade
|
||||||
|
Route:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
description: Route Id
|
||||||
|
type: string
|
||||||
|
network_type:
|
||||||
|
description: Network type indicating if it is IPv4 or IPv6
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- network_type
|
||||||
|
- $ref: '#/components/schemas/RouteRequest'
|
||||||
|
RoutePatchOperation:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PatchMinimum'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
description: Route field to update in form /<field>
|
||||||
|
type: string
|
||||||
|
enum: [ "network","network_id","description","enabled","peer","metric","masquerade" ]
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
Nameserver:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ip:
|
||||||
|
description: Nameserver IP
|
||||||
|
type: string
|
||||||
|
ns_type:
|
||||||
|
description: Nameserver Type
|
||||||
|
type: string
|
||||||
|
enum: ["udp"]
|
||||||
|
port:
|
||||||
|
description: Nameserver Port
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- ip
|
||||||
|
- ns_type
|
||||||
|
- port
|
||||||
|
NameserverGroupRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: Nameserver group name
|
||||||
|
type: string
|
||||||
|
maxLength: 40
|
||||||
|
minLength: 1
|
||||||
|
description:
|
||||||
|
description: Nameserver group description
|
||||||
|
type: string
|
||||||
|
nameservers:
|
||||||
|
description: Nameserver group
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 2
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Nameserver'
|
||||||
|
enabled:
|
||||||
|
description: Nameserver group status
|
||||||
|
type: boolean
|
||||||
|
groups:
|
||||||
|
description: Nameserver group tag groups
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- op
|
- name
|
||||||
- path
|
- description
|
||||||
- value
|
- nameservers
|
||||||
|
- enabled
|
||||||
|
- groups
|
||||||
|
NameserverGroup:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
description: Nameserver group ID
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- $ref: '#/components/schemas/NameserverGroupRequest'
|
||||||
|
NameserverGroupPatchOperation:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PatchMinimum'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
description: Nameserver group field to update in form /<field>
|
||||||
|
type: string
|
||||||
|
enum: [ "name","description","enabled","groups","nameservers" ]
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
|
||||||
responses:
|
responses:
|
||||||
not_found:
|
not_found:
|
||||||
description: Resource not found
|
description: Resource not found
|
||||||
@@ -329,6 +525,67 @@ paths:
|
|||||||
"$ref": "#/components/responses/forbidden"
|
"$ref": "#/components/responses/forbidden"
|
||||||
'500':
|
'500':
|
||||||
"$ref": "#/components/responses/internal_error"
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
/api/users/:
|
||||||
|
post:
|
||||||
|
summary: Create a User (invite)
|
||||||
|
tags: [ Users]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
requestBody:
|
||||||
|
description: User invite information
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserCreateRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A User object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/User'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
/api/users/{id}:
|
||||||
|
put:
|
||||||
|
summary: Update information about a User
|
||||||
|
tags: [ Users]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The User ID
|
||||||
|
requestBody:
|
||||||
|
description: User update
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A User object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/User'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
/api/peers:
|
/api/peers:
|
||||||
get:
|
get:
|
||||||
summary: Returns a list of all peers
|
summary: Returns a list of all peers
|
||||||
@@ -945,5 +1202,346 @@ paths:
|
|||||||
"$ref": "#/components/responses/requires_authentication"
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
'403':
|
'403':
|
||||||
"$ref": "#/components/responses/forbidden"
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
|
||||||
|
/api/routes:
|
||||||
|
get:
|
||||||
|
summary: Returns a list of all routes
|
||||||
|
tags: [ Routes ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A JSON Array of Routes
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Route'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
post:
|
||||||
|
summary: Creates a Route
|
||||||
|
tags: [ Routes ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
requestBody:
|
||||||
|
description: New Routes request
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RouteRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Route Object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Route'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
|
||||||
|
/api/routes/{id}:
|
||||||
|
get:
|
||||||
|
summary: Get information about a Routes
|
||||||
|
tags: [ Routes ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Route ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Route object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Route'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
put:
|
||||||
|
summary: Update/Replace a Route
|
||||||
|
tags: [ Routes ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Route ID
|
||||||
|
requestBody:
|
||||||
|
description: Update Route request
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RouteRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Route object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Route'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
patch:
|
||||||
|
summary: Update information about a Route
|
||||||
|
tags: [ Routes ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Route ID
|
||||||
|
requestBody:
|
||||||
|
description: Update Route request using a list of json patch objects
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/RoutePatchOperation'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Route object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Route'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
delete:
|
||||||
|
summary: Delete a Route
|
||||||
|
tags: [ Routes ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Route ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Delete status code
|
||||||
|
content: { }
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
/api/dns/nameservers:
|
||||||
|
get:
|
||||||
|
summary: Returns a list of all Nameserver Groups
|
||||||
|
tags: [ DNS ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A JSON Array of Nameserver Groups
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/NameserverGroup'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
post:
|
||||||
|
summary: Creates a Nameserver Group
|
||||||
|
tags: [ DNS ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
requestBody:
|
||||||
|
description: New Nameserver Groups request
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/NameserverGroupRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Nameserver Groups Object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/NameserverGroup'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
|
||||||
|
/api/dns/nameservers/{id}:
|
||||||
|
get:
|
||||||
|
summary: Get information about a Nameserver Groups
|
||||||
|
tags: [ DNS ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Nameserver Group ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Nameserver Group object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/NameserverGroup'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
put:
|
||||||
|
summary: Update/Replace a Nameserver Group
|
||||||
|
tags: [ DNS ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Nameserver Group ID
|
||||||
|
requestBody:
|
||||||
|
description: Update Nameserver Group request
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/NameserverGroupRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Nameserver Group object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/NameserverGroup'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
patch:
|
||||||
|
summary: Update information about a Nameserver Group
|
||||||
|
tags: [ DNS ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Nameserver Group ID
|
||||||
|
requestBody:
|
||||||
|
description: Update Nameserver Group request using a list of json patch objects
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/NameserverGroupPatchOperation'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Nameserver Group object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/NameserverGroup'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
delete:
|
||||||
|
summary: Delete a Nameserver Group
|
||||||
|
tags: [ DNS ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Nameserver Group ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Delete status code
|
||||||
|
content: { }
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
'500':
|
'500':
|
||||||
"$ref": "#/components/responses/internal_error"
|
"$ref": "#/components/responses/internal_error"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package api provides primitives to interact with the openapi HTTP API.
|
// Package api provides primitives to interact with the openapi HTTP API.
|
||||||
//
|
//
|
||||||
// Code generated by github.com/deepmap/oapi-codegen version v1.11.0 DO NOT EDIT.
|
// Code generated by github.com/deepmap/oapi-codegen version v1.11.1-0.20220912230023-4a1477f6a8ba DO NOT EDIT.
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -24,6 +24,52 @@ const (
|
|||||||
GroupPatchOperationPathPeers GroupPatchOperationPath = "peers"
|
GroupPatchOperationPathPeers GroupPatchOperationPath = "peers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Defines values for NameserverNsType.
|
||||||
|
const (
|
||||||
|
NameserverNsTypeUdp NameserverNsType = "udp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for NameserverGroupPatchOperationOp.
|
||||||
|
const (
|
||||||
|
NameserverGroupPatchOperationOpAdd NameserverGroupPatchOperationOp = "add"
|
||||||
|
NameserverGroupPatchOperationOpRemove NameserverGroupPatchOperationOp = "remove"
|
||||||
|
NameserverGroupPatchOperationOpReplace NameserverGroupPatchOperationOp = "replace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for NameserverGroupPatchOperationPath.
|
||||||
|
const (
|
||||||
|
NameserverGroupPatchOperationPathDescription NameserverGroupPatchOperationPath = "description"
|
||||||
|
NameserverGroupPatchOperationPathEnabled NameserverGroupPatchOperationPath = "enabled"
|
||||||
|
NameserverGroupPatchOperationPathGroups NameserverGroupPatchOperationPath = "groups"
|
||||||
|
NameserverGroupPatchOperationPathName NameserverGroupPatchOperationPath = "name"
|
||||||
|
NameserverGroupPatchOperationPathNameservers NameserverGroupPatchOperationPath = "nameservers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for PatchMinimumOp.
|
||||||
|
const (
|
||||||
|
PatchMinimumOpAdd PatchMinimumOp = "add"
|
||||||
|
PatchMinimumOpRemove PatchMinimumOp = "remove"
|
||||||
|
PatchMinimumOpReplace PatchMinimumOp = "replace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for RoutePatchOperationOp.
|
||||||
|
const (
|
||||||
|
RoutePatchOperationOpAdd RoutePatchOperationOp = "add"
|
||||||
|
RoutePatchOperationOpRemove RoutePatchOperationOp = "remove"
|
||||||
|
RoutePatchOperationOpReplace RoutePatchOperationOp = "replace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for RoutePatchOperationPath.
|
||||||
|
const (
|
||||||
|
RoutePatchOperationPathDescription RoutePatchOperationPath = "description"
|
||||||
|
RoutePatchOperationPathEnabled RoutePatchOperationPath = "enabled"
|
||||||
|
RoutePatchOperationPathMasquerade RoutePatchOperationPath = "masquerade"
|
||||||
|
RoutePatchOperationPathMetric RoutePatchOperationPath = "metric"
|
||||||
|
RoutePatchOperationPathNetwork RoutePatchOperationPath = "network"
|
||||||
|
RoutePatchOperationPathNetworkId RoutePatchOperationPath = "network_id"
|
||||||
|
RoutePatchOperationPathPeer RoutePatchOperationPath = "peer"
|
||||||
|
)
|
||||||
|
|
||||||
// Defines values for RulePatchOperationOp.
|
// Defines values for RulePatchOperationOp.
|
||||||
const (
|
const (
|
||||||
RulePatchOperationOpAdd RulePatchOperationOp = "add"
|
RulePatchOperationOpAdd RulePatchOperationOp = "add"
|
||||||
@@ -41,216 +87,427 @@ const (
|
|||||||
RulePatchOperationPathSources RulePatchOperationPath = "sources"
|
RulePatchOperationPathSources RulePatchOperationPath = "sources"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Defines values for UserStatus.
|
||||||
|
const (
|
||||||
|
UserStatusActive UserStatus = "active"
|
||||||
|
UserStatusDisabled UserStatus = "disabled"
|
||||||
|
UserStatusInvited UserStatus = "invited"
|
||||||
|
)
|
||||||
|
|
||||||
// Group defines model for Group.
|
// Group defines model for Group.
|
||||||
type Group struct {
|
type Group struct {
|
||||||
// Group ID
|
// Id Group ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Group Name identifier
|
// Name Group Name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// List of peers object
|
// Peers List of peers object
|
||||||
Peers []PeerMinimum `json:"peers"`
|
Peers []PeerMinimum `json:"peers"`
|
||||||
|
|
||||||
// Count of peers associated to the group
|
// PeersCount Count of peers associated to the group
|
||||||
PeersCount int `json:"peers_count"`
|
PeersCount int `json:"peers_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupMinimum defines model for GroupMinimum.
|
// GroupMinimum defines model for GroupMinimum.
|
||||||
type GroupMinimum struct {
|
type GroupMinimum struct {
|
||||||
// Group ID
|
// Id Group ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Group Name identifier
|
// Name Group Name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// Count of peers associated to the group
|
// PeersCount Count of peers associated to the group
|
||||||
PeersCount int `json:"peers_count"`
|
PeersCount int `json:"peers_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupPatchOperation defines model for GroupPatchOperation.
|
// GroupPatchOperation defines model for GroupPatchOperation.
|
||||||
type GroupPatchOperation struct {
|
type GroupPatchOperation struct {
|
||||||
// Patch operation type
|
// Op Patch operation type
|
||||||
Op GroupPatchOperationOp `json:"op"`
|
Op GroupPatchOperationOp `json:"op"`
|
||||||
|
|
||||||
// Group field to update in form /<field>
|
// Path Group field to update in form /<field>
|
||||||
Path GroupPatchOperationPath `json:"path"`
|
Path GroupPatchOperationPath `json:"path"`
|
||||||
|
|
||||||
// Values to be applied
|
// Value Values to be applied
|
||||||
Value []string `json:"value"`
|
Value []string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch operation type
|
// GroupPatchOperationOp Patch operation type
|
||||||
type GroupPatchOperationOp string
|
type GroupPatchOperationOp string
|
||||||
|
|
||||||
// Group field to update in form /<field>
|
// GroupPatchOperationPath Group field to update in form /<field>
|
||||||
type GroupPatchOperationPath string
|
type GroupPatchOperationPath string
|
||||||
|
|
||||||
|
// Nameserver defines model for Nameserver.
|
||||||
|
type Nameserver struct {
|
||||||
|
// Ip Nameserver IP
|
||||||
|
Ip string `json:"ip"`
|
||||||
|
|
||||||
|
// NsType Nameserver Type
|
||||||
|
NsType NameserverNsType `json:"ns_type"`
|
||||||
|
|
||||||
|
// Port Nameserver Port
|
||||||
|
Port int `json:"port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameserverNsType Nameserver Type
|
||||||
|
type NameserverNsType string
|
||||||
|
|
||||||
|
// NameserverGroup defines model for NameserverGroup.
|
||||||
|
type NameserverGroup struct {
|
||||||
|
// Description Nameserver group description
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Enabled Nameserver group status
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
|
// Groups Nameserver group tag groups
|
||||||
|
Groups []string `json:"groups"`
|
||||||
|
|
||||||
|
// Id Nameserver group ID
|
||||||
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
// Name Nameserver group name
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Nameservers Nameserver group
|
||||||
|
Nameservers []Nameserver `json:"nameservers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameserverGroupPatchOperation defines model for NameserverGroupPatchOperation.
|
||||||
|
type NameserverGroupPatchOperation struct {
|
||||||
|
// Op Patch operation type
|
||||||
|
Op NameserverGroupPatchOperationOp `json:"op"`
|
||||||
|
|
||||||
|
// Path Nameserver group field to update in form /<field>
|
||||||
|
Path NameserverGroupPatchOperationPath `json:"path"`
|
||||||
|
|
||||||
|
// Value Values to be applied
|
||||||
|
Value []string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameserverGroupPatchOperationOp Patch operation type
|
||||||
|
type NameserverGroupPatchOperationOp string
|
||||||
|
|
||||||
|
// NameserverGroupPatchOperationPath Nameserver group field to update in form /<field>
|
||||||
|
type NameserverGroupPatchOperationPath string
|
||||||
|
|
||||||
|
// NameserverGroupRequest defines model for NameserverGroupRequest.
|
||||||
|
type NameserverGroupRequest struct {
|
||||||
|
// Description Nameserver group description
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Enabled Nameserver group status
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
|
// Groups Nameserver group tag groups
|
||||||
|
Groups []string `json:"groups"`
|
||||||
|
|
||||||
|
// Name Nameserver group name
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Nameservers Nameserver group
|
||||||
|
Nameservers []Nameserver `json:"nameservers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchMinimum defines model for PatchMinimum.
|
||||||
|
type PatchMinimum struct {
|
||||||
|
// Op Patch operation type
|
||||||
|
Op PatchMinimumOp `json:"op"`
|
||||||
|
|
||||||
|
// Value Values to be applied
|
||||||
|
Value []string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchMinimumOp Patch operation type
|
||||||
|
type PatchMinimumOp string
|
||||||
|
|
||||||
// Peer defines model for Peer.
|
// Peer defines model for Peer.
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
// Provides information of who activated the Peer. User or Setup Key
|
// Connected Peer to Management connection status
|
||||||
ActivatedBy struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
} `json:"activated_by"`
|
|
||||||
|
|
||||||
// Peer to Management connection status
|
|
||||||
Connected bool `json:"connected"`
|
Connected bool `json:"connected"`
|
||||||
|
|
||||||
// Groups that the peer belongs to
|
// Groups Groups that the peer belongs to
|
||||||
Groups []GroupMinimum `json:"groups"`
|
Groups []GroupMinimum `json:"groups"`
|
||||||
|
|
||||||
// Peer ID
|
// Hostname Hostname of the machine
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
|
||||||
|
// Id Peer ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Peer's IP address
|
// Ip Peer's IP address
|
||||||
Ip string `json:"ip"`
|
Ip string `json:"ip"`
|
||||||
|
|
||||||
// Last time peer connected to Netbird's management service
|
// LastSeen Last time peer connected to Netbird's management service
|
||||||
LastSeen time.Time `json:"last_seen"`
|
LastSeen time.Time `json:"last_seen"`
|
||||||
|
|
||||||
// Peer's hostname
|
// Name Peer's hostname
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// Peer's operating system and version
|
// Os Peer's operating system and version
|
||||||
Os string `json:"os"`
|
Os string `json:"os"`
|
||||||
|
|
||||||
// Indicates whether SSH server is enabled on this peer
|
// SshEnabled Indicates whether SSH server is enabled on this peer
|
||||||
SshEnabled bool `json:"ssh_enabled"`
|
SshEnabled bool `json:"ssh_enabled"`
|
||||||
|
|
||||||
// Peer's daemon or cli version
|
// UiVersion Peer's desktop UI version
|
||||||
|
UiVersion *string `json:"ui_version,omitempty"`
|
||||||
|
|
||||||
|
// UserId User ID of the user that enrolled this peer
|
||||||
|
UserId *string `json:"user_id,omitempty"`
|
||||||
|
|
||||||
|
// Version Peer's daemon or cli version
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerMinimum defines model for PeerMinimum.
|
// PeerMinimum defines model for PeerMinimum.
|
||||||
type PeerMinimum struct {
|
type PeerMinimum struct {
|
||||||
// Peer ID
|
// Id Peer ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Peer's hostname
|
// Name Peer's hostname
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Route defines model for Route.
|
||||||
|
type Route struct {
|
||||||
|
// Description Route description
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Enabled Route status
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
|
// Id Route Id
|
||||||
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
// Masquerade Indicate if peer should masquerade traffic to this route's prefix
|
||||||
|
Masquerade bool `json:"masquerade"`
|
||||||
|
|
||||||
|
// Metric Route metric number. Lowest number has higher priority
|
||||||
|
Metric int `json:"metric"`
|
||||||
|
|
||||||
|
// Network Network range in CIDR format
|
||||||
|
Network string `json:"network"`
|
||||||
|
|
||||||
|
// NetworkId Route network identifier, to group HA routes
|
||||||
|
NetworkId string `json:"network_id"`
|
||||||
|
|
||||||
|
// NetworkType Network type indicating if it is IPv4 or IPv6
|
||||||
|
NetworkType string `json:"network_type"`
|
||||||
|
|
||||||
|
// Peer Peer Identifier associated with route
|
||||||
|
Peer string `json:"peer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoutePatchOperation defines model for RoutePatchOperation.
|
||||||
|
type RoutePatchOperation struct {
|
||||||
|
// Op Patch operation type
|
||||||
|
Op RoutePatchOperationOp `json:"op"`
|
||||||
|
|
||||||
|
// Path Route field to update in form /<field>
|
||||||
|
Path RoutePatchOperationPath `json:"path"`
|
||||||
|
|
||||||
|
// Value Values to be applied
|
||||||
|
Value []string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoutePatchOperationOp Patch operation type
|
||||||
|
type RoutePatchOperationOp string
|
||||||
|
|
||||||
|
// RoutePatchOperationPath Route field to update in form /<field>
|
||||||
|
type RoutePatchOperationPath string
|
||||||
|
|
||||||
|
// RouteRequest defines model for RouteRequest.
|
||||||
|
type RouteRequest struct {
|
||||||
|
// Description Route description
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Enabled Route status
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
|
// Masquerade Indicate if peer should masquerade traffic to this route's prefix
|
||||||
|
Masquerade bool `json:"masquerade"`
|
||||||
|
|
||||||
|
// Metric Route metric number. Lowest number has higher priority
|
||||||
|
Metric int `json:"metric"`
|
||||||
|
|
||||||
|
// Network Network range in CIDR format
|
||||||
|
Network string `json:"network"`
|
||||||
|
|
||||||
|
// NetworkId Route network identifier, to group HA routes
|
||||||
|
NetworkId string `json:"network_id"`
|
||||||
|
|
||||||
|
// Peer Peer Identifier associated with route
|
||||||
|
Peer string `json:"peer"`
|
||||||
|
}
|
||||||
|
|
||||||
// Rule defines model for Rule.
|
// Rule defines model for Rule.
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
// Rule friendly description
|
// Description Rule friendly description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
|
||||||
// Rule destination groups
|
// Destinations Rule destination groups
|
||||||
Destinations []GroupMinimum `json:"destinations"`
|
Destinations []GroupMinimum `json:"destinations"`
|
||||||
|
|
||||||
// Rules status
|
// Disabled Rules status
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
|
|
||||||
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
// Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
|
|
||||||
// Rule ID
|
// Id Rule ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Rule name identifier
|
// Name Rule name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// Rule source groups
|
// Sources Rule source groups
|
||||||
Sources []GroupMinimum `json:"sources"`
|
Sources []GroupMinimum `json:"sources"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RuleMinimum defines model for RuleMinimum.
|
// RuleMinimum defines model for RuleMinimum.
|
||||||
type RuleMinimum struct {
|
type RuleMinimum struct {
|
||||||
// Rule friendly description
|
// Description Rule friendly description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
|
||||||
// Rules status
|
// Disabled Rules status
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
|
|
||||||
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
// Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
|
|
||||||
// Rule name identifier
|
// Name Rule name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RulePatchOperation defines model for RulePatchOperation.
|
// RulePatchOperation defines model for RulePatchOperation.
|
||||||
type RulePatchOperation struct {
|
type RulePatchOperation struct {
|
||||||
// Patch operation type
|
// Op Patch operation type
|
||||||
Op RulePatchOperationOp `json:"op"`
|
Op RulePatchOperationOp `json:"op"`
|
||||||
|
|
||||||
// Rule field to update in form /<field>
|
// Path Rule field to update in form /<field>
|
||||||
Path RulePatchOperationPath `json:"path"`
|
Path RulePatchOperationPath `json:"path"`
|
||||||
|
|
||||||
// Values to be applied
|
// Value Values to be applied
|
||||||
Value []string `json:"value"`
|
Value []string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch operation type
|
// RulePatchOperationOp Patch operation type
|
||||||
type RulePatchOperationOp string
|
type RulePatchOperationOp string
|
||||||
|
|
||||||
// Rule field to update in form /<field>
|
// RulePatchOperationPath Rule field to update in form /<field>
|
||||||
type RulePatchOperationPath string
|
type RulePatchOperationPath string
|
||||||
|
|
||||||
// SetupKey defines model for SetupKey.
|
// SetupKey defines model for SetupKey.
|
||||||
type SetupKey struct {
|
type SetupKey struct {
|
||||||
// Setup Key expiration date
|
// AutoGroups Setup key groups to auto-assign to peers registered with this key
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
|
// Expires Setup Key expiration date
|
||||||
Expires time.Time `json:"expires"`
|
Expires time.Time `json:"expires"`
|
||||||
|
|
||||||
// Setup Key ID
|
// Id Setup Key ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Setup Key value
|
// Key Setup Key value
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
|
|
||||||
// Setup key last usage date
|
// LastUsed Setup key last usage date
|
||||||
LastUsed time.Time `json:"last_used"`
|
LastUsed time.Time `json:"last_used"`
|
||||||
|
|
||||||
// Setup key name identifier
|
// Name Setup key name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// Setup key revocation status
|
// Revoked Setup key revocation status
|
||||||
Revoked bool `json:"revoked"`
|
Revoked bool `json:"revoked"`
|
||||||
|
|
||||||
// Setup key status, "valid", "overused","expired" or "revoked"
|
// State Setup key status, "valid", "overused","expired" or "revoked"
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
|
|
||||||
// Setup key type, one-off for single time usage and reusable
|
// Type Setup key type, one-off for single time usage and reusable
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
||||||
// Usage count of setup key
|
// UpdatedAt Setup key last update date
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
|
// UsedTimes Usage count of setup key
|
||||||
UsedTimes int `json:"used_times"`
|
UsedTimes int `json:"used_times"`
|
||||||
|
|
||||||
// Setup key validity status
|
// Valid Setup key validity status
|
||||||
Valid bool `json:"valid"`
|
Valid bool `json:"valid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupKeyRequest defines model for SetupKeyRequest.
|
// SetupKeyRequest defines model for SetupKeyRequest.
|
||||||
type SetupKeyRequest struct {
|
type SetupKeyRequest struct {
|
||||||
// Expiration time in seconds
|
// AutoGroups Setup key groups to auto-assign to peers registered with this key
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
|
// ExpiresIn Expiration time in seconds
|
||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
|
||||||
// Setup Key name
|
// Name Setup Key name
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// Setup key revocation status
|
// Revoked Setup key revocation status
|
||||||
Revoked bool `json:"revoked"`
|
Revoked bool `json:"revoked"`
|
||||||
|
|
||||||
// Setup key type, one-off for single time usage and reusable
|
// Type Setup key type, one-off for single time usage and reusable
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// User defines model for User.
|
// User defines model for User.
|
||||||
type User struct {
|
type User struct {
|
||||||
// User's email address
|
// AutoGroups Groups to auto-assign to peers registered by this user
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
|
// Email User's email address
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
|
||||||
// User ID
|
// Id User ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// User's name from idp provider
|
// Name User's name from idp provider
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// User's Netbird account role
|
// Role User's NetBird account role
|
||||||
|
Role string `json:"role"`
|
||||||
|
|
||||||
|
// Status User's status
|
||||||
|
Status UserStatus `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserStatus User's status
|
||||||
|
type UserStatus string
|
||||||
|
|
||||||
|
// UserCreateRequest defines model for UserCreateRequest.
|
||||||
|
type UserCreateRequest struct {
|
||||||
|
// AutoGroups Groups to auto-assign to peers registered by this user
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
|
// Email User's Email to send invite to
|
||||||
|
Email string `json:"email"`
|
||||||
|
|
||||||
|
// Name User's full name
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// Role User's NetBird account role
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserRequest defines model for UserRequest.
|
||||||
|
type UserRequest struct {
|
||||||
|
// AutoGroups Groups to auto-assign to peers registered by this user
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
|
// Role User's NetBird account role
|
||||||
|
Role string `json:"role"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchApiDnsNameserversIdJSONBody defines parameters for PatchApiDnsNameserversId.
|
||||||
|
type PatchApiDnsNameserversIdJSONBody = []NameserverGroupPatchOperation
|
||||||
|
|
||||||
// PostApiGroupsJSONBody defines parameters for PostApiGroups.
|
// PostApiGroupsJSONBody defines parameters for PostApiGroups.
|
||||||
type PostApiGroupsJSONBody struct {
|
type PostApiGroupsJSONBody struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -272,19 +529,22 @@ type PutApiPeersIdJSONBody struct {
|
|||||||
SshEnabled bool `json:"ssh_enabled"`
|
SshEnabled bool `json:"ssh_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PatchApiRoutesIdJSONBody defines parameters for PatchApiRoutesId.
|
||||||
|
type PatchApiRoutesIdJSONBody = []RoutePatchOperation
|
||||||
|
|
||||||
// PostApiRulesJSONBody defines parameters for PostApiRules.
|
// PostApiRulesJSONBody defines parameters for PostApiRules.
|
||||||
type PostApiRulesJSONBody struct {
|
type PostApiRulesJSONBody struct {
|
||||||
// Rule friendly description
|
// Description Rule friendly description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Destinations *[]string `json:"destinations,omitempty"`
|
Destinations *[]string `json:"destinations,omitempty"`
|
||||||
|
|
||||||
// Rules status
|
// Disabled Rules status
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
|
|
||||||
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
// Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
|
|
||||||
// Rule name identifier
|
// Name Rule name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Sources *[]string `json:"sources,omitempty"`
|
Sources *[]string `json:"sources,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -294,26 +554,29 @@ type PatchApiRulesIdJSONBody = []RulePatchOperation
|
|||||||
|
|
||||||
// PutApiRulesIdJSONBody defines parameters for PutApiRulesId.
|
// PutApiRulesIdJSONBody defines parameters for PutApiRulesId.
|
||||||
type PutApiRulesIdJSONBody struct {
|
type PutApiRulesIdJSONBody struct {
|
||||||
// Rule friendly description
|
// Description Rule friendly description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Destinations *[]string `json:"destinations,omitempty"`
|
Destinations *[]string `json:"destinations,omitempty"`
|
||||||
|
|
||||||
// Rules status
|
// Disabled Rules status
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
|
|
||||||
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
// Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
|
|
||||||
// Rule name identifier
|
// Name Rule name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Sources *[]string `json:"sources,omitempty"`
|
Sources *[]string `json:"sources,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostApiSetupKeysJSONBody defines parameters for PostApiSetupKeys.
|
// PostApiDnsNameserversJSONRequestBody defines body for PostApiDnsNameservers for application/json ContentType.
|
||||||
type PostApiSetupKeysJSONBody = SetupKeyRequest
|
type PostApiDnsNameserversJSONRequestBody = NameserverGroupRequest
|
||||||
|
|
||||||
// PutApiSetupKeysIdJSONBody defines parameters for PutApiSetupKeysId.
|
// PatchApiDnsNameserversIdJSONRequestBody defines body for PatchApiDnsNameserversId for application/json ContentType.
|
||||||
type PutApiSetupKeysIdJSONBody = SetupKeyRequest
|
type PatchApiDnsNameserversIdJSONRequestBody = PatchApiDnsNameserversIdJSONBody
|
||||||
|
|
||||||
|
// PutApiDnsNameserversIdJSONRequestBody defines body for PutApiDnsNameserversId for application/json ContentType.
|
||||||
|
type PutApiDnsNameserversIdJSONRequestBody = NameserverGroupRequest
|
||||||
|
|
||||||
// PostApiGroupsJSONRequestBody defines body for PostApiGroups for application/json ContentType.
|
// PostApiGroupsJSONRequestBody defines body for PostApiGroups for application/json ContentType.
|
||||||
type PostApiGroupsJSONRequestBody PostApiGroupsJSONBody
|
type PostApiGroupsJSONRequestBody PostApiGroupsJSONBody
|
||||||
@@ -327,6 +590,15 @@ type PutApiGroupsIdJSONRequestBody PutApiGroupsIdJSONBody
|
|||||||
// PutApiPeersIdJSONRequestBody defines body for PutApiPeersId for application/json ContentType.
|
// PutApiPeersIdJSONRequestBody defines body for PutApiPeersId for application/json ContentType.
|
||||||
type PutApiPeersIdJSONRequestBody PutApiPeersIdJSONBody
|
type PutApiPeersIdJSONRequestBody PutApiPeersIdJSONBody
|
||||||
|
|
||||||
|
// PostApiRoutesJSONRequestBody defines body for PostApiRoutes for application/json ContentType.
|
||||||
|
type PostApiRoutesJSONRequestBody = RouteRequest
|
||||||
|
|
||||||
|
// PatchApiRoutesIdJSONRequestBody defines body for PatchApiRoutesId for application/json ContentType.
|
||||||
|
type PatchApiRoutesIdJSONRequestBody = PatchApiRoutesIdJSONBody
|
||||||
|
|
||||||
|
// PutApiRoutesIdJSONRequestBody defines body for PutApiRoutesId for application/json ContentType.
|
||||||
|
type PutApiRoutesIdJSONRequestBody = RouteRequest
|
||||||
|
|
||||||
// PostApiRulesJSONRequestBody defines body for PostApiRules for application/json ContentType.
|
// PostApiRulesJSONRequestBody defines body for PostApiRules for application/json ContentType.
|
||||||
type PostApiRulesJSONRequestBody PostApiRulesJSONBody
|
type PostApiRulesJSONRequestBody PostApiRulesJSONBody
|
||||||
|
|
||||||
@@ -337,7 +609,13 @@ type PatchApiRulesIdJSONRequestBody = PatchApiRulesIdJSONBody
|
|||||||
type PutApiRulesIdJSONRequestBody PutApiRulesIdJSONBody
|
type PutApiRulesIdJSONRequestBody PutApiRulesIdJSONBody
|
||||||
|
|
||||||
// PostApiSetupKeysJSONRequestBody defines body for PostApiSetupKeys for application/json ContentType.
|
// PostApiSetupKeysJSONRequestBody defines body for PostApiSetupKeys for application/json ContentType.
|
||||||
type PostApiSetupKeysJSONRequestBody = PostApiSetupKeysJSONBody
|
type PostApiSetupKeysJSONRequestBody = SetupKeyRequest
|
||||||
|
|
||||||
// PutApiSetupKeysIdJSONRequestBody defines body for PutApiSetupKeysId for application/json ContentType.
|
// PutApiSetupKeysIdJSONRequestBody defines body for PutApiSetupKeysId for application/json ContentType.
|
||||||
type PutApiSetupKeysIdJSONRequestBody = PutApiSetupKeysIdJSONBody
|
type PutApiSetupKeysIdJSONRequestBody = SetupKeyRequest
|
||||||
|
|
||||||
|
// PostApiUsersJSONRequestBody defines body for PostApiUsers for application/json ContentType.
|
||||||
|
type PostApiUsersJSONRequestBody = UserCreateRequest
|
||||||
|
|
||||||
|
// PutApiUsersIdJSONRequestBody defines body for PutApiUsersId for application/json ContentType.
|
||||||
|
type PutApiUsersIdJSONRequestBody = UserRequest
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -67,14 +67,14 @@ func initGroupTestData(groups ...*server.Group) *Groups {
|
|||||||
}
|
}
|
||||||
return nil, fmt.Errorf("peer not found")
|
return nil, fmt.Errorf("peer not found")
|
||||||
},
|
},
|
||||||
GetAccountWithAuthorizationClaimsFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
||||||
return &server.Account{
|
return &server.Account{
|
||||||
Id: claims.AccountId,
|
Id: claims.AccountId,
|
||||||
Domain: "hotmail.com",
|
Domain: "hotmail.com",
|
||||||
Peers: TestPeers,
|
Peers: TestPeers,
|
||||||
Groups: map[string]*server.Group{
|
Groups: map[string]*server.Group{
|
||||||
"id-existed": &server.Group{ID: "id-existed", Peers: []string{"A", "B"}},
|
"id-existed": {ID: "id-existed", Peers: []string{"A", "B"}},
|
||||||
"id-all": &server.Group{ID: "id-all", Name: "All"}},
|
"id-all": {ID: "id-all", Name: "All"}},
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
81
management/server/http/handler.go
Normal file
81
management/server/http/handler.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
s "github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/middleware"
|
||||||
|
"github.com/rs/cors"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIHandler creates the Management service HTTP API handler registering all the available endpoints.
|
||||||
|
func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience string, authKeysLocation string) (http.Handler, error) {
|
||||||
|
jwtMiddleware, err := middleware.NewJwtMiddleware(
|
||||||
|
authIssuer,
|
||||||
|
authAudience,
|
||||||
|
authKeysLocation,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
corsMiddleware := cors.AllowAll()
|
||||||
|
|
||||||
|
acMiddleware := middleware.NewAccessControll(
|
||||||
|
authAudience,
|
||||||
|
accountManager.IsUserAdmin)
|
||||||
|
|
||||||
|
apiHandler := mux.NewRouter()
|
||||||
|
apiHandler.Use(corsMiddleware.Handler, jwtMiddleware.Handler, acMiddleware.Handler)
|
||||||
|
|
||||||
|
groupsHandler := NewGroups(accountManager, authAudience)
|
||||||
|
rulesHandler := NewRules(accountManager, authAudience)
|
||||||
|
peersHandler := NewPeers(accountManager, authAudience)
|
||||||
|
keysHandler := NewSetupKeysHandler(accountManager, authAudience)
|
||||||
|
userHandler := NewUserHandler(accountManager, authAudience)
|
||||||
|
routesHandler := NewRoutes(accountManager, authAudience)
|
||||||
|
nameserversHandler := NewNameservers(accountManager, authAudience)
|
||||||
|
|
||||||
|
apiHandler.HandleFunc("/api/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).
|
||||||
|
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/users", userHandler.GetUsers).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/users/{id}", userHandler.UpdateUser).Methods("PUT", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/users", userHandler.CreateUserHandler).Methods("POST", "OPTIONS")
|
||||||
|
|
||||||
|
apiHandler.HandleFunc("/api/setup-keys", keysHandler.GetAllSetupKeysHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/setup-keys", keysHandler.CreateSetupKeyHandler).Methods("POST", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/setup-keys/{id}", keysHandler.GetSetupKeyHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/setup-keys/{id}", keysHandler.UpdateSetupKeyHandler).Methods("PUT", "OPTIONS")
|
||||||
|
|
||||||
|
apiHandler.HandleFunc("/api/rules", rulesHandler.GetAllRulesHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/rules", rulesHandler.CreateRuleHandler).Methods("POST", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/rules/{id}", rulesHandler.UpdateRuleHandler).Methods("PUT", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/rules/{id}", rulesHandler.PatchRuleHandler).Methods("PATCH", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/rules/{id}", rulesHandler.GetRuleHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/rules/{id}", rulesHandler.DeleteRuleHandler).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
|
apiHandler.HandleFunc("/api/groups", groupsHandler.GetAllGroupsHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/groups", groupsHandler.CreateGroupHandler).Methods("POST", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.UpdateGroupHandler).Methods("PUT", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.PatchGroupHandler).Methods("PATCH", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.GetGroupHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.DeleteGroupHandler).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
|
apiHandler.HandleFunc("/api/routes", routesHandler.GetAllRoutesHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/routes", routesHandler.CreateRouteHandler).Methods("POST", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.UpdateRouteHandler).Methods("PUT", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.PatchRouteHandler).Methods("PATCH", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.GetRouteHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.DeleteRouteHandler).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
|
apiHandler.HandleFunc("/api/dns/nameservers", nameserversHandler.GetAllNameserversHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/dns/nameservers", nameserversHandler.CreateNameserverGroupHandler).Methods("POST", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/dns/nameservers/{id}", nameserversHandler.UpdateNameserverGroupHandler).Methods("PUT", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/dns/nameservers/{id}", nameserversHandler.PatchNameserverGroupHandler).Methods("PATCH", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/dns/nameservers/{id}", nameserversHandler.GetNameserverGroupHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/dns/nameservers/{id}", nameserversHandler.DeleteNameserverGroupHandler).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
|
return apiHandler, nil
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetupKeys is a handler that returns a list of setup keys of the account
|
|
||||||
type SetupKeys struct {
|
|
||||||
accountManager server.AccountManager
|
|
||||||
jwtExtractor jwtclaims.ClaimsExtractor
|
|
||||||
authAudience string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSetupKeysHandler(accountManager server.AccountManager, authAudience string) *SetupKeys {
|
|
||||||
return &SetupKeys{
|
|
||||||
accountManager: accountManager,
|
|
||||||
authAudience: authAudience,
|
|
||||||
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SetupKeys) updateKey(accountId string, keyId string, w http.ResponseWriter, r *http.Request) {
|
|
||||||
req := &api.PutApiSetupKeysIdJSONRequestBody{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var key *server.SetupKey
|
|
||||||
if req.Revoked {
|
|
||||||
//handle only if being revoked, don't allow to enable key again for now
|
|
||||||
key, err = h.accountManager.RevokeSetupKey(accountId, keyId)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "failed revoking key", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(req.Name) != 0 {
|
|
||||||
key, err = h.accountManager.RenameSetupKey(accountId, keyId, req.Name)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "failed renaming key", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if key != nil {
|
|
||||||
writeSuccess(w, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SetupKeys) getKey(accountId string, keyId string, w http.ResponseWriter, r *http.Request) {
|
|
||||||
account, err := h.accountManager.GetAccountById(accountId)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "account doesn't exist", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, key := range account.SetupKeys {
|
|
||||||
if key.Id == keyId {
|
|
||||||
writeSuccess(w, key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http.Error(w, "setup key not found", http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.Request) {
|
|
||||||
req := &api.PostApiSetupKeysJSONRequestBody{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Name == "" {
|
|
||||||
http.Error(w, "Setup key name shouldn't be empty", http.StatusUnprocessableEntity)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(server.SetupKeyType(req.Type) == server.SetupKeyReusable ||
|
|
||||||
server.SetupKeyType(req.Type) == server.SetupKeyOneOff) {
|
|
||||||
|
|
||||||
http.Error(w, "unknown setup key type "+string(req.Type), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expiresIn := time.Duration(req.ExpiresIn) * time.Second
|
|
||||||
|
|
||||||
setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, server.SetupKeyType(req.Type), expiresIn)
|
|
||||||
if err != nil {
|
|
||||||
errStatus, ok := status.FromError(err)
|
|
||||||
if ok && errStatus.Code() == codes.NotFound {
|
|
||||||
http.Error(w, "account not found", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
http.Error(w, "failed adding setup key", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeSuccess(w, setupKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
keyId := vars["id"]
|
|
||||||
if len(keyId) == 0 {
|
|
||||||
http.Error(w, "invalid key Id", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodPut:
|
|
||||||
h.updateKey(account.Id, keyId, w, r)
|
|
||||||
return
|
|
||||||
case http.MethodGet:
|
|
||||||
h.getKey(account.Id, keyId, w, r)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
http.Error(w, "", http.StatusNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodPost:
|
|
||||||
h.createKey(account.Id, w, r)
|
|
||||||
return
|
|
||||||
case http.MethodGet:
|
|
||||||
w.WriteHeader(200)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
respBody := []*api.SetupKey{}
|
|
||||||
for _, key := range account.SetupKeys {
|
|
||||||
respBody = append(respBody, toResponseBody(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(respBody)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed encoding account peers %s: %v", account.Id, err)
|
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
http.Error(w, "", http.StatusNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeSuccess(w http.ResponseWriter, key *server.SetupKey) {
|
|
||||||
w.WriteHeader(200)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
err := json.NewEncoder(w).Encode(toResponseBody(key))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "failed handling request", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toResponseBody(key *server.SetupKey) *api.SetupKey {
|
|
||||||
var state string
|
|
||||||
if key.IsExpired() {
|
|
||||||
state = "expired"
|
|
||||||
} else if key.IsRevoked() {
|
|
||||||
state = "revoked"
|
|
||||||
} else if key.IsOverUsed() {
|
|
||||||
state = "overused"
|
|
||||||
} else {
|
|
||||||
state = "valid"
|
|
||||||
}
|
|
||||||
return &api.SetupKey{
|
|
||||||
Id: key.Id,
|
|
||||||
Key: key.Key,
|
|
||||||
Name: key.Name,
|
|
||||||
Expires: key.ExpiresAt,
|
|
||||||
Type: string(key.Type),
|
|
||||||
Valid: key.IsValid(),
|
|
||||||
Revoked: key.Revoked,
|
|
||||||
UsedTimes: key.UsedTimes,
|
|
||||||
LastUsed: key.LastUsed,
|
|
||||||
State: state,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UserHandler struct {
|
|
||||||
accountManager server.AccountManager
|
|
||||||
authAudience string
|
|
||||||
jwtExtractor jwtclaims.ClaimsExtractor
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUserHandler(accountManager server.AccountManager, authAudience string) *UserHandler {
|
|
||||||
return &UserHandler{
|
|
||||||
accountManager: accountManager,
|
|
||||||
authAudience: authAudience,
|
|
||||||
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsers returns a list of users of the account this user belongs to.
|
|
||||||
// It also gathers additional user data (like email and name) from the IDP manager.
|
|
||||||
func (h *UserHandler) GetUsers(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
http.Error(w, "", http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := h.accountManager.GetUsersFromAccount(account.Id)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
users := []*api.User{}
|
|
||||||
for _, r := range data {
|
|
||||||
users = append(users, toUserResponse(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
writeJSONObject(w, users)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toUserResponse(user *server.UserInfo) *api.User {
|
|
||||||
return &api.User{
|
|
||||||
Id: user.ID,
|
|
||||||
Name: user.Name,
|
|
||||||
Email: user.Email,
|
|
||||||
Role: user.Role,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
286
management/server/http/nameservers.go
Normal file
286
management/server/http/nameservers.go
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Nameservers is the nameserver group handler of the account
|
||||||
|
type Nameservers struct {
|
||||||
|
jwtExtractor jwtclaims.ClaimsExtractor
|
||||||
|
accountManager server.AccountManager
|
||||||
|
authAudience string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNameservers returns a new instance of Nameservers handler
|
||||||
|
func NewNameservers(accountManager server.AccountManager, authAudience string) *Nameservers {
|
||||||
|
return &Nameservers{
|
||||||
|
accountManager: accountManager,
|
||||||
|
authAudience: authAudience,
|
||||||
|
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllNameserversHandler returns the list of nameserver groups for the account
|
||||||
|
func (h *Nameservers) GetAllNameserversHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroups, err := h.accountManager.ListNameServerGroups(account.Id)
|
||||||
|
if err != nil {
|
||||||
|
toHTTPError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiNameservers := make([]*api.NameserverGroup, 0)
|
||||||
|
for _, r := range nsGroups {
|
||||||
|
apiNameservers = append(apiNameservers, toNameserverGroupResponse(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSONObject(w, apiNameservers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNameserverGroupHandler handles nameserver group creation request
|
||||||
|
func (h *Nameservers) CreateNameserverGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req api.PostApiDnsNameserversJSONRequestBody
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsList, err := toServerNSList(req.Nameservers)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroup, err := h.accountManager.CreateNameServerGroup(account.Id, req.Name, req.Description, nsList, req.Groups, req.Enabled)
|
||||||
|
if err != nil {
|
||||||
|
toHTTPError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toNameserverGroupResponse(nsGroup)
|
||||||
|
|
||||||
|
writeJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNameserverGroupHandler handles update to a nameserver group identified by a given ID
|
||||||
|
func (h *Nameservers) UpdateNameserverGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroupID := mux.Vars(r)["id"]
|
||||||
|
if len(nsGroupID) == 0 {
|
||||||
|
http.Error(w, "invalid nameserver group ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PutApiDnsNameserversIdJSONRequestBody
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsList, err := toServerNSList(req.Nameservers)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedNSGroup := &nbdns.NameServerGroup{
|
||||||
|
ID: nsGroupID,
|
||||||
|
Name: req.Name,
|
||||||
|
Description: req.Description,
|
||||||
|
NameServers: nsList,
|
||||||
|
Groups: req.Groups,
|
||||||
|
Enabled: req.Enabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.accountManager.SaveNameServerGroup(account.Id, updatedNSGroup)
|
||||||
|
if err != nil {
|
||||||
|
toHTTPError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toNameserverGroupResponse(updatedNSGroup)
|
||||||
|
|
||||||
|
writeJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchNameserverGroupHandler handles patch updates to a nameserver group identified by a given ID
|
||||||
|
func (h *Nameservers) PatchNameserverGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroupID := mux.Vars(r)["id"]
|
||||||
|
if len(nsGroupID) == 0 {
|
||||||
|
http.Error(w, "invalid nameserver group ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PatchApiDnsNameserversIdJSONRequestBody
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var operations []server.NameServerGroupUpdateOperation
|
||||||
|
for _, patch := range req {
|
||||||
|
if patch.Op != api.NameserverGroupPatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("nameserver groups only accepts replace operations, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch patch.Path {
|
||||||
|
case api.NameserverGroupPatchOperationPathName:
|
||||||
|
operations = append(operations, server.NameServerGroupUpdateOperation{
|
||||||
|
Type: server.UpdateNameServerGroupName,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.NameserverGroupPatchOperationPathDescription:
|
||||||
|
operations = append(operations, server.NameServerGroupUpdateOperation{
|
||||||
|
Type: server.UpdateNameServerGroupDescription,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.NameserverGroupPatchOperationPathNameservers:
|
||||||
|
operations = append(operations, server.NameServerGroupUpdateOperation{
|
||||||
|
Type: server.UpdateNameServerGroupNameServers,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.NameserverGroupPatchOperationPathGroups:
|
||||||
|
operations = append(operations, server.NameServerGroupUpdateOperation{
|
||||||
|
Type: server.UpdateNameServerGroupGroups,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.NameserverGroupPatchOperationPathEnabled:
|
||||||
|
operations = append(operations, server.NameServerGroupUpdateOperation{
|
||||||
|
Type: server.UpdateNameServerGroupEnabled,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
http.Error(w, "invalid patch path", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedNSGroup, err := h.accountManager.UpdateNameServerGroup(account.Id, nsGroupID, operations)
|
||||||
|
if err != nil {
|
||||||
|
toHTTPError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toNameserverGroupResponse(updatedNSGroup)
|
||||||
|
|
||||||
|
writeJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteNameserverGroupHandler handles nameserver group deletion request
|
||||||
|
func (h *Nameservers) DeleteNameserverGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroupID := mux.Vars(r)["id"]
|
||||||
|
if len(nsGroupID) == 0 {
|
||||||
|
http.Error(w, "invalid nameserver group ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.accountManager.DeleteNameServerGroup(account.Id, nsGroupID)
|
||||||
|
if err != nil {
|
||||||
|
toHTTPError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSONObject(w, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNameserverGroupHandler handles a nameserver group Get request identified by ID
|
||||||
|
func (h *Nameservers) GetNameserverGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroupID := mux.Vars(r)["id"]
|
||||||
|
if len(nsGroupID) == 0 {
|
||||||
|
http.Error(w, "invalid nameserver group ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroup, err := h.accountManager.GetNameServerGroup(account.Id, nsGroupID)
|
||||||
|
if err != nil {
|
||||||
|
toHTTPError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toNameserverGroupResponse(nsGroup)
|
||||||
|
|
||||||
|
writeJSONObject(w, &resp)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func toServerNSList(apiNSList []api.Nameserver) ([]nbdns.NameServer, error) {
|
||||||
|
var nsList []nbdns.NameServer
|
||||||
|
for _, apiNS := range apiNSList {
|
||||||
|
parsed, err := nbdns.ParseNameServerURL(fmt.Sprintf("%s://%s:%d", apiNS.NsType, apiNS.Ip, apiNS.Port))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nsList = append(nsList, parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nsList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toNameserverGroupResponse(serverNSGroup *nbdns.NameServerGroup) *api.NameserverGroup {
|
||||||
|
var nsList []api.Nameserver
|
||||||
|
for _, ns := range serverNSGroup.NameServers {
|
||||||
|
apiNS := api.Nameserver{
|
||||||
|
Ip: ns.IP.String(),
|
||||||
|
NsType: api.NameserverNsType(ns.NSType.String()),
|
||||||
|
Port: ns.Port,
|
||||||
|
}
|
||||||
|
nsList = append(nsList, apiNS)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.NameserverGroup{
|
||||||
|
Id: serverNSGroup.ID,
|
||||||
|
Name: serverNSGroup.Name,
|
||||||
|
Description: serverNSGroup.Description,
|
||||||
|
Groups: serverNSGroup.Groups,
|
||||||
|
Nameservers: nsList,
|
||||||
|
Enabled: serverNSGroup.Enabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
287
management/server/http/nameservers_test.go
Normal file
287
management/server/http/nameservers_test.go
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
existingNSGroupID = "existingNSGroupID"
|
||||||
|
notFoundNSGroupID = "notFoundNSGroupID"
|
||||||
|
testNSGroupAccountID = "test_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testingNSAccount = &server.Account{
|
||||||
|
Id: testNSGroupAccountID,
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseExistingNSGroup = &nbdns.NameServerGroup{
|
||||||
|
ID: existingNSGroupID,
|
||||||
|
Name: "super",
|
||||||
|
Description: "super",
|
||||||
|
NameServers: []nbdns.NameServer{
|
||||||
|
{
|
||||||
|
IP: netip.MustParseAddr("1.1.1.1"),
|
||||||
|
NSType: nbdns.UDPNameServerType,
|
||||||
|
Port: nbdns.DefaultDNSPort,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IP: netip.MustParseAddr("1.1.2.2"),
|
||||||
|
NSType: nbdns.UDPNameServerType,
|
||||||
|
Port: nbdns.DefaultDNSPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Groups: []string{"testing"},
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func initNameserversTestData() *Nameservers {
|
||||||
|
return &Nameservers{
|
||||||
|
accountManager: &mock_server.MockAccountManager{
|
||||||
|
GetNameServerGroupFunc: func(accountID, nsGroupID string) (*nbdns.NameServerGroup, error) {
|
||||||
|
if nsGroupID == existingNSGroupID {
|
||||||
|
return baseExistingNSGroup.Copy(), nil
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.NotFound, "nameserver group with ID %s not found", nsGroupID)
|
||||||
|
},
|
||||||
|
CreateNameServerGroupFunc: func(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, enabled bool) (*nbdns.NameServerGroup, error) {
|
||||||
|
return &nbdns.NameServerGroup{
|
||||||
|
ID: existingNSGroupID,
|
||||||
|
Name: name,
|
||||||
|
Description: description,
|
||||||
|
NameServers: nameServerList,
|
||||||
|
Groups: groups,
|
||||||
|
Enabled: enabled,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
DeleteNameServerGroupFunc: func(accountID, nsGroupID string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
SaveNameServerGroupFunc: func(accountID string, nsGroupToSave *nbdns.NameServerGroup) error {
|
||||||
|
if nsGroupToSave.ID == existingNSGroupID {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return status.Errorf(codes.NotFound, "nameserver group with ID %s was not found", nsGroupToSave.ID)
|
||||||
|
},
|
||||||
|
UpdateNameServerGroupFunc: func(accountID, nsGroupID string, operations []server.NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error) {
|
||||||
|
nsGroupToUpdate := baseExistingNSGroup.Copy()
|
||||||
|
if nsGroupID != nsGroupToUpdate.ID {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "nameserver group ID %s no longer exists", nsGroupID)
|
||||||
|
}
|
||||||
|
for _, operation := range operations {
|
||||||
|
switch operation.Type {
|
||||||
|
case server.UpdateNameServerGroupName:
|
||||||
|
nsGroupToUpdate.Name = operation.Values[0]
|
||||||
|
case server.UpdateNameServerGroupDescription:
|
||||||
|
nsGroupToUpdate.Description = operation.Values[0]
|
||||||
|
case server.UpdateNameServerGroupNameServers:
|
||||||
|
var parsedNSList []nbdns.NameServer
|
||||||
|
for _, nsURL := range operation.Values {
|
||||||
|
parsed, err := nbdns.ParseNameServerURL(nsURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
parsedNSList = append(parsedNSList, parsed)
|
||||||
|
}
|
||||||
|
nsGroupToUpdate.NameServers = parsedNSList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nsGroupToUpdate, nil
|
||||||
|
},
|
||||||
|
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
||||||
|
return testingNSAccount, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authAudience: "",
|
||||||
|
jwtExtractor: jwtclaims.ClaimsExtractor{
|
||||||
|
ExtractClaimsFromRequestContext: func(r *http.Request, authAudiance string) jwtclaims.AuthorizationClaims {
|
||||||
|
return jwtclaims.AuthorizationClaims{
|
||||||
|
UserId: "test_user",
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
AccountId: testNSGroupAccountID,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNameserversHandlers(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
expectedStatus int
|
||||||
|
expectedBody bool
|
||||||
|
expectedNSGroup *api.NameserverGroup
|
||||||
|
requestType string
|
||||||
|
requestPath string
|
||||||
|
requestBody io.Reader
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get Existing Nameserver Group",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/dns/nameservers/" + existingNSGroupID,
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedNSGroup: toNameserverGroupResponse(baseExistingNSGroup),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get Not Existing Nameserver Group",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/dns/nameservers/" + notFoundNSGroupID,
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST OK",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/dns/nameservers",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"1.1.1.1\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true}")),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedNSGroup: &api.NameserverGroup{
|
||||||
|
Id: existingNSGroupID,
|
||||||
|
Name: "name",
|
||||||
|
Description: "Post",
|
||||||
|
Nameservers: []api.Nameserver{
|
||||||
|
{
|
||||||
|
Ip: "1.1.1.1",
|
||||||
|
NsType: "udp",
|
||||||
|
Port: 53,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Groups: []string{"group"},
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST Invalid Nameserver",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/dns/nameservers",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"1000\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true}")),
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT OK",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/dns/nameservers/" + existingNSGroupID,
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"1.1.1.1\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true}")),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedNSGroup: &api.NameserverGroup{
|
||||||
|
Id: existingNSGroupID,
|
||||||
|
Name: "name",
|
||||||
|
Description: "Post",
|
||||||
|
Nameservers: []api.Nameserver{
|
||||||
|
{
|
||||||
|
Ip: "1.1.1.1",
|
||||||
|
NsType: "udp",
|
||||||
|
Port: 53,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Groups: []string{"group"},
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT Not Existing Nameserver Group",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/dns/nameservers/" + notFoundNSGroupID,
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"1.1.1.1\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true}")),
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT Invalid Nameserver",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/dns/nameservers/" + notFoundNSGroupID,
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"100\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true}")),
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PATCH OK",
|
||||||
|
requestType: http.MethodPatch,
|
||||||
|
requestPath: "/api/dns/nameservers/" + existingNSGroupID,
|
||||||
|
requestBody: bytes.NewBufferString("[{\"op\":\"replace\",\"path\":\"description\",\"value\":[\"NewDesc\"]}]"),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedNSGroup: &api.NameserverGroup{
|
||||||
|
Id: existingNSGroupID,
|
||||||
|
Name: baseExistingNSGroup.Name,
|
||||||
|
Description: "NewDesc",
|
||||||
|
Nameservers: toNameserverGroupResponse(baseExistingNSGroup).Nameservers,
|
||||||
|
Groups: baseExistingNSGroup.Groups,
|
||||||
|
Enabled: baseExistingNSGroup.Enabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PATCH Invalid Nameserver Group OK",
|
||||||
|
requestType: http.MethodPatch,
|
||||||
|
requestPath: "/api/dns/nameservers/" + notFoundRouteID,
|
||||||
|
requestBody: bytes.NewBufferString("[{\"op\":\"replace\",\"path\":\"description\",\"value\":[\"NewDesc\"]}]"),
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
p := initNameserversTestData()
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
|
||||||
|
|
||||||
|
router := mux.NewRouter()
|
||||||
|
router.HandleFunc("/api/dns/nameservers/{id}", p.GetNameserverGroupHandler).Methods("GET")
|
||||||
|
router.HandleFunc("/api/dns/nameservers", p.CreateNameserverGroupHandler).Methods("POST")
|
||||||
|
router.HandleFunc("/api/dns/nameservers/{id}", p.DeleteNameserverGroupHandler).Methods("DELETE")
|
||||||
|
router.HandleFunc("/api/dns/nameservers/{id}", p.UpdateNameserverGroupHandler).Methods("PUT")
|
||||||
|
router.HandleFunc("/api/dns/nameservers/{id}", p.PatchNameserverGroupHandler).Methods("PATCH")
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
res := recorder.Result()
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
content, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("I don't know what I expected; %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status := recorder.Code; status != tc.expectedStatus {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v, content: %s",
|
||||||
|
status, tc.expectedStatus, string(content))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectedBody {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
got := &api.NameserverGroup{}
|
||||||
|
if err = json.Unmarshal(content, &got); err != nil {
|
||||||
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tc.expectedNSGroup, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Peers is a handler that returns peers of the account
|
// Peers is a handler that returns peers of the account
|
||||||
type Peers struct {
|
type Peers struct {
|
||||||
accountManager server.AccountManager
|
accountManager server.AccountManager
|
||||||
authAudience string
|
authAudience string
|
||||||
@@ -144,5 +144,8 @@ func toPeerResponse(peer *server.Peer, account *server.Account) *api.Peer {
|
|||||||
Version: peer.Meta.WtVersion,
|
Version: peer.Meta.WtVersion,
|
||||||
Groups: groupsInfo,
|
Groups: groupsInfo,
|
||||||
SshEnabled: peer.SSHEnabled,
|
SshEnabled: peer.SSHEnabled,
|
||||||
|
Hostname: peer.Meta.Hostname,
|
||||||
|
UserId: &peer.UserID,
|
||||||
|
UiVersion: &peer.Meta.UIVersion,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
func initTestMetaData(peer ...*server.Peer) *Peers {
|
func initTestMetaData(peer ...*server.Peer) *Peers {
|
||||||
return &Peers{
|
return &Peers{
|
||||||
accountManager: &mock_server.MockAccountManager{
|
accountManager: &mock_server.MockAccountManager{
|
||||||
GetAccountWithAuthorizationClaimsFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
||||||
return &server.Account{
|
return &server.Account{
|
||||||
Id: claims.AccountId,
|
Id: claims.AccountId,
|
||||||
Domain: "hotmail.com",
|
Domain: "hotmail.com",
|
||||||
408
management/server/http/routes.go
Normal file
408
management/server/http/routes.go
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"net/http"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Routes is the routes handler of the account
|
||||||
|
type Routes struct {
|
||||||
|
jwtExtractor jwtclaims.ClaimsExtractor
|
||||||
|
accountManager server.AccountManager
|
||||||
|
authAudience string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRoutes returns a new instance of Routes handler
|
||||||
|
func NewRoutes(accountManager server.AccountManager, authAudience string) *Routes {
|
||||||
|
return &Routes{
|
||||||
|
accountManager: accountManager,
|
||||||
|
authAudience: authAudience,
|
||||||
|
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllRoutesHandler returns the list of routes for the account
|
||||||
|
func (h *Routes) GetAllRoutesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
routes, err := h.accountManager.ListRoutes(account.Id)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiRoutes := make([]*api.Route, 0)
|
||||||
|
for _, r := range routes {
|
||||||
|
apiRoutes = append(apiRoutes, toRouteResponse(account, r))
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSONObject(w, apiRoutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRouteHandler handles route creation request
|
||||||
|
func (h *Routes) CreateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PostApiRoutesJSONRequestBody
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKey := req.Peer
|
||||||
|
if req.Peer != "" {
|
||||||
|
peer, err := h.accountManager.GetPeerByIP(account.Id, req.Peer)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peerKey = peer.Key
|
||||||
|
}
|
||||||
|
|
||||||
|
_, newPrefix, err := route.ParseNetwork(req.Network)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("couldn't parse update prefix %s", req.Network), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if utf8.RuneCountInString(req.NetworkId) > route.MaxNetIDChar || req.NetworkId == "" {
|
||||||
|
http.Error(w, fmt.Sprintf("identifier should be between 1 and %d", route.MaxNetIDChar), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newRoute, err := h.accountManager.CreateRoute(account.Id, newPrefix.String(), peerKey, req.Description, req.NetworkId, req.Masquerade, req.Metric, req.Enabled)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toRouteResponse(account, newRoute)
|
||||||
|
|
||||||
|
writeJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRouteHandler handles update to a route identified by a given ID
|
||||||
|
func (h *Routes) UpdateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
routeID := vars["id"]
|
||||||
|
if len(routeID) == 0 {
|
||||||
|
http.Error(w, "invalid route Id", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.accountManager.GetRoute(account.Id, routeID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("couldn't find route for ID %s", routeID), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PutApiRoutesIdJSONRequestBody
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixType, newPrefix, err := route.ParseNetwork(req.Network)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("couldn't parse update prefix %s for route ID %s", req.Network, routeID), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKey := req.Peer
|
||||||
|
if req.Peer != "" {
|
||||||
|
peer, err := h.accountManager.GetPeerByIP(account.Id, req.Peer)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peerKey = peer.Key
|
||||||
|
}
|
||||||
|
|
||||||
|
if utf8.RuneCountInString(req.NetworkId) > route.MaxNetIDChar || req.NetworkId == "" {
|
||||||
|
http.Error(w, fmt.Sprintf("identifier should be between 1 and %d", route.MaxNetIDChar), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newRoute := &route.Route{
|
||||||
|
ID: routeID,
|
||||||
|
Network: newPrefix,
|
||||||
|
NetID: req.NetworkId,
|
||||||
|
NetworkType: prefixType,
|
||||||
|
Masquerade: req.Masquerade,
|
||||||
|
Peer: peerKey,
|
||||||
|
Metric: req.Metric,
|
||||||
|
Description: req.Description,
|
||||||
|
Enabled: req.Enabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.accountManager.SaveRoute(account.Id, newRoute)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed updating route \"%s\" under account %s %v", routeID, account.Id, err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toRouteResponse(account, newRoute)
|
||||||
|
|
||||||
|
writeJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchRouteHandler handles patch updates to a route identified by a given ID
|
||||||
|
func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
routeID := vars["id"]
|
||||||
|
if len(routeID) == 0 {
|
||||||
|
http.Error(w, "invalid route ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.accountManager.GetRoute(account.Id, routeID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Error(w, fmt.Sprintf("couldn't find route ID %s", routeID), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PatchApiRoutesIdJSONRequestBody
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req) == 0 {
|
||||||
|
http.Error(w, "no patch instruction received", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var operations []server.RouteUpdateOperation
|
||||||
|
|
||||||
|
for _, patch := range req {
|
||||||
|
switch patch.Path {
|
||||||
|
case api.RoutePatchOperationPathNetwork:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Network field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRouteNetwork,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.RoutePatchOperationPathDescription:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Description field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRouteDescription,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.RoutePatchOperationPathNetworkId:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Network Identifier field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRouteNetworkIdentifier,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.RoutePatchOperationPathPeer:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Peer field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(patch.Value) > 1 {
|
||||||
|
http.Error(w, fmt.Sprintf("Value field only accepts 1 value, got %d", len(patch.Value)),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peerValue := patch.Value
|
||||||
|
if patch.Value[0] != "" {
|
||||||
|
peer, err := h.accountManager.GetPeerByIP(account.Id, patch.Value[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peerValue = []string{peer.Key}
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRoutePeer,
|
||||||
|
Values: peerValue,
|
||||||
|
})
|
||||||
|
case api.RoutePatchOperationPathMetric:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Metric field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRouteMetric,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.RoutePatchOperationPathMasquerade:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Masquerade field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRouteMasquerade,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.RoutePatchOperationPathEnabled:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Enabled field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRouteEnabled,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
http.Error(w, "invalid patch path", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
route, err := h.accountManager.UpdateRoute(account.Id, routeID, operations)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errStatus, ok := status.FromError(err)
|
||||||
|
if ok && errStatus.Code() == codes.Internal {
|
||||||
|
http.Error(w, errStatus.String(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok && errStatus.Code() == codes.NotFound {
|
||||||
|
http.Error(w, errStatus.String(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok && errStatus.Code() == codes.InvalidArgument {
|
||||||
|
http.Error(w, errStatus.String(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Errorf("failed updating route %s under account %s %v", routeID, account.Id, err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toRouteResponse(account, route)
|
||||||
|
|
||||||
|
writeJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRouteHandler handles route deletion request
|
||||||
|
func (h *Routes) DeleteRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
routeID := mux.Vars(r)["id"]
|
||||||
|
if len(routeID) == 0 {
|
||||||
|
http.Error(w, "invalid route ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.accountManager.DeleteRoute(account.Id, routeID)
|
||||||
|
if err != nil {
|
||||||
|
errStatus, ok := status.FromError(err)
|
||||||
|
if ok && errStatus.Code() == codes.NotFound {
|
||||||
|
http.Error(w, fmt.Sprintf("route %s not found under account %s", routeID, account.Id), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Errorf("failed delete route %s under account %s %v", routeID, account.Id, err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSONObject(w, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRouteHandler handles a route Get request identified by ID
|
||||||
|
func (h *Routes) GetRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
routeID := mux.Vars(r)["id"]
|
||||||
|
if len(routeID) == 0 {
|
||||||
|
http.Error(w, "invalid route ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foundRoute, err := h.accountManager.GetRoute(account.Id, routeID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "route not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSONObject(w, toRouteResponse(account, foundRoute))
|
||||||
|
}
|
||||||
|
|
||||||
|
func toRouteResponse(account *server.Account, serverRoute *route.Route) *api.Route {
|
||||||
|
var peerIP string
|
||||||
|
if serverRoute.Peer != "" {
|
||||||
|
peer, found := account.Peers[serverRoute.Peer]
|
||||||
|
if !found {
|
||||||
|
panic("peer ID not found")
|
||||||
|
}
|
||||||
|
peerIP = peer.IP.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.Route{
|
||||||
|
Id: serverRoute.ID,
|
||||||
|
Description: serverRoute.Description,
|
||||||
|
NetworkId: serverRoute.NetID,
|
||||||
|
Enabled: serverRoute.Enabled,
|
||||||
|
Peer: peerIP,
|
||||||
|
Network: serverRoute.Network.String(),
|
||||||
|
NetworkType: serverRoute.NetworkType.String(),
|
||||||
|
Masquerade: serverRoute.Masquerade,
|
||||||
|
Metric: serverRoute.Metric,
|
||||||
|
}
|
||||||
|
}
|
||||||
365
management/server/http/routes_test.go
Normal file
365
management/server/http/routes_test.go
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/magiconair/properties/assert"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
existingRouteID = "existingRouteID"
|
||||||
|
notFoundRouteID = "notFoundRouteID"
|
||||||
|
existingPeerID = "100.64.0.100"
|
||||||
|
notFoundPeerID = "100.64.0.200"
|
||||||
|
existingPeerKey = "existingPeerKey"
|
||||||
|
testAccountID = "test_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
var baseExistingRoute = &route.Route{
|
||||||
|
ID: existingRouteID,
|
||||||
|
Description: "base route",
|
||||||
|
NetID: "awesomeNet",
|
||||||
|
Network: netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
NetworkType: route.IPv4Network,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var testingAccount = &server.Account{
|
||||||
|
Id: testAccountID,
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
Peers: map[string]*server.Peer{
|
||||||
|
existingPeerKey: {
|
||||||
|
Key: existingPeerID,
|
||||||
|
IP: netip.MustParseAddr(existingPeerID).AsSlice(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func initRoutesTestData() *Routes {
|
||||||
|
return &Routes{
|
||||||
|
accountManager: &mock_server.MockAccountManager{
|
||||||
|
GetRouteFunc: func(_, routeID string) (*route.Route, error) {
|
||||||
|
if routeID == existingRouteID {
|
||||||
|
return baseExistingRoute, nil
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.NotFound, "route with ID %s not found", routeID)
|
||||||
|
},
|
||||||
|
CreateRouteFunc: func(accountID string, network, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error) {
|
||||||
|
networkType, p, _ := route.ParseNetwork(network)
|
||||||
|
return &route.Route{
|
||||||
|
ID: existingRouteID,
|
||||||
|
NetID: netID,
|
||||||
|
Peer: peer,
|
||||||
|
Network: p,
|
||||||
|
NetworkType: networkType,
|
||||||
|
Description: description,
|
||||||
|
Masquerade: masquerade,
|
||||||
|
Enabled: enabled,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
SaveRouteFunc: func(_ string, _ *route.Route) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
DeleteRouteFunc: func(_ string, peerIP string) error {
|
||||||
|
if peerIP != existingRouteID {
|
||||||
|
return status.Errorf(codes.NotFound, "Peer with ID %s not found", peerIP)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
||||||
|
if peerIP != existingPeerID {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "Peer with ID %s not found", peerIP)
|
||||||
|
}
|
||||||
|
return &server.Peer{
|
||||||
|
Key: existingPeerKey,
|
||||||
|
IP: netip.MustParseAddr(existingPeerID).AsSlice(),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
UpdateRouteFunc: func(_ string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error) {
|
||||||
|
routeToUpdate := baseExistingRoute
|
||||||
|
if routeID != routeToUpdate.ID {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "route %s no longer exists", routeID)
|
||||||
|
}
|
||||||
|
for _, operation := range operations {
|
||||||
|
switch operation.Type {
|
||||||
|
case server.UpdateRouteNetwork:
|
||||||
|
routeToUpdate.NetworkType, routeToUpdate.Network, _ = route.ParseNetwork(operation.Values[0])
|
||||||
|
case server.UpdateRouteDescription:
|
||||||
|
routeToUpdate.Description = operation.Values[0]
|
||||||
|
case server.UpdateRouteNetworkIdentifier:
|
||||||
|
routeToUpdate.NetID = operation.Values[0]
|
||||||
|
case server.UpdateRoutePeer:
|
||||||
|
routeToUpdate.Peer = operation.Values[0]
|
||||||
|
case server.UpdateRouteMetric:
|
||||||
|
routeToUpdate.Metric, _ = strconv.Atoi(operation.Values[0])
|
||||||
|
case server.UpdateRouteMasquerade:
|
||||||
|
routeToUpdate.Masquerade, _ = strconv.ParseBool(operation.Values[0])
|
||||||
|
case server.UpdateRouteEnabled:
|
||||||
|
routeToUpdate.Enabled, _ = strconv.ParseBool(operation.Values[0])
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("no operation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return routeToUpdate, nil
|
||||||
|
},
|
||||||
|
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
||||||
|
return testingAccount, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authAudience: "",
|
||||||
|
jwtExtractor: jwtclaims.ClaimsExtractor{
|
||||||
|
ExtractClaimsFromRequestContext: func(r *http.Request, authAudiance string) jwtclaims.AuthorizationClaims {
|
||||||
|
return jwtclaims.AuthorizationClaims{
|
||||||
|
UserId: "test_user",
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
AccountId: testAccountID,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoutesHandlers(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
expectedStatus int
|
||||||
|
expectedBody bool
|
||||||
|
expectedRoute *api.Route
|
||||||
|
requestType string
|
||||||
|
requestPath string
|
||||||
|
requestBody io.Reader
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get Existing Route",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedRoute: toRouteResponse(testingAccount, baseExistingRoute),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get Not Existing Route",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/routes/" + notFoundRouteID,
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Delete Existing Route",
|
||||||
|
requestType: http.MethodDelete,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Delete Not Existing Route",
|
||||||
|
requestType: http.MethodDelete,
|
||||||
|
requestPath: "/api/routes/" + notFoundRouteID,
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST OK",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/routes",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID))),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedRoute: &api.Route{
|
||||||
|
Id: existingRouteID,
|
||||||
|
Description: "Post",
|
||||||
|
NetworkId: "awesomeNet",
|
||||||
|
Network: "192.168.0.0/16",
|
||||||
|
Peer: existingPeerID,
|
||||||
|
NetworkType: route.IPv4NetworkString,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST Not Found Peer",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/routes",
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", notFoundPeerID)),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST Not Invalid Network Identifier",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/routes",
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST Invalid Network",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/routes",
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT OK",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedRoute: &api.Route{
|
||||||
|
Id: existingRouteID,
|
||||||
|
Description: "Post",
|
||||||
|
NetworkId: "awesomeNet",
|
||||||
|
Network: "192.168.0.0/16",
|
||||||
|
Peer: existingPeerID,
|
||||||
|
NetworkType: route.IPv4NetworkString,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT Not Found Route",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + notFoundRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT Not Found Peer",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", notFoundPeerID)),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT Invalid Network Identifier",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT Invalid Network",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PATCH Description OK",
|
||||||
|
requestType: http.MethodPatch,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString("[{\"op\":\"replace\",\"path\":\"description\",\"value\":[\"NewDesc\"]}]"),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedRoute: &api.Route{
|
||||||
|
Id: existingRouteID,
|
||||||
|
Description: "NewDesc",
|
||||||
|
NetworkId: "awesomeNet",
|
||||||
|
Network: baseExistingRoute.Network.String(),
|
||||||
|
NetworkType: route.IPv4NetworkString,
|
||||||
|
Masquerade: baseExistingRoute.Masquerade,
|
||||||
|
Enabled: baseExistingRoute.Enabled,
|
||||||
|
Metric: baseExistingRoute.Metric,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PATCH Peer OK",
|
||||||
|
requestType: http.MethodPatch,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("[{\"op\":\"replace\",\"path\":\"peer\",\"value\":[\"%s\"]}]", existingPeerID)),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedRoute: &api.Route{
|
||||||
|
Id: existingRouteID,
|
||||||
|
Description: "NewDesc",
|
||||||
|
NetworkId: "awesomeNet",
|
||||||
|
Network: baseExistingRoute.Network.String(),
|
||||||
|
NetworkType: route.IPv4NetworkString,
|
||||||
|
Peer: existingPeerID,
|
||||||
|
Masquerade: baseExistingRoute.Masquerade,
|
||||||
|
Enabled: baseExistingRoute.Enabled,
|
||||||
|
Metric: baseExistingRoute.Metric,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PATCH Not Found Peer",
|
||||||
|
requestType: http.MethodPatch,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("[{\"op\":\"replace\",\"path\":\"peer\",\"value\":[\"%s\"]}]", notFoundPeerID)),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PATCH Not Found Route",
|
||||||
|
requestType: http.MethodPatch,
|
||||||
|
requestPath: "/api/routes/" + notFoundRouteID,
|
||||||
|
requestBody: bytes.NewBufferString("[{\"op\":\"replace\",\"path\":\"network\",\"value\":[\"192.168.0.0/34\"]}]"),
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
p := initRoutesTestData()
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
|
||||||
|
|
||||||
|
router := mux.NewRouter()
|
||||||
|
router.HandleFunc("/api/routes/{id}", p.GetRouteHandler).Methods("GET")
|
||||||
|
router.HandleFunc("/api/routes/{id}", p.DeleteRouteHandler).Methods("DELETE")
|
||||||
|
router.HandleFunc("/api/routes", p.CreateRouteHandler).Methods("POST")
|
||||||
|
router.HandleFunc("/api/routes/{id}", p.UpdateRouteHandler).Methods("PUT")
|
||||||
|
router.HandleFunc("/api/routes/{id}", p.PatchRouteHandler).Methods("PATCH")
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
res := recorder.Result()
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
content, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("I don't know what I expected; %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status := recorder.Code; status != tc.expectedStatus {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v, content: %s",
|
||||||
|
status, tc.expectedStatus, string(content))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectedBody {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
got := &api.Route{}
|
||||||
|
if err = json.Unmarshal(content, &got); err != nil {
|
||||||
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, got, tc.expectedRoute)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user