mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-06 17:08:53 +00:00
Compare commits
26 Commits
documentat
...
v0.8.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e34e0ccd12 | ||
|
|
95dc9cc16c | ||
|
|
d1c2b3d703 | ||
|
|
966661fe91 | ||
|
|
67ddaade58 | ||
|
|
138cf35e00 | ||
|
|
2555a6c3e8 | ||
|
|
86a66c6202 | ||
|
|
275d364df6 | ||
|
|
a3c5fa1307 | ||
|
|
75a69ca26b | ||
|
|
ae8e3ad6fe | ||
|
|
ff729f6755 | ||
|
|
7e1b20da5d | ||
|
|
d4a3ee9d87 | ||
|
|
49e9113e0f | ||
|
|
3bdfa3cc8e | ||
|
|
8c953c5a2c | ||
|
|
e95f0f7acb | ||
|
|
fa7b413fe7 | ||
|
|
295f0c755a | ||
|
|
a98f6f840a | ||
|
|
faad5a1e98 | ||
|
|
e8caa562b0 | ||
|
|
1aafc15607 | ||
|
|
06860c4c10 |
6
.github/workflows/golang-test-darwin.yml
vendored
6
.github/workflows/golang-test-darwin.yml
vendored
@@ -1,16 +1,14 @@
|
|||||||
name: Test Code Darwin
|
name: Test Code Darwin
|
||||||
on: [push,pull_request]
|
on: [push,pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go-version: [1.18.x]
|
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: 1.18.x
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
|||||||
7
.github/workflows/golang-test-linux.yml
vendored
7
.github/workflows/golang-test-linux.yml
vendored
@@ -1,16 +1,17 @@
|
|||||||
name: Test Code Linux
|
name: Test Code Linux
|
||||||
on: [push,pull_request]
|
on: [push,pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x]
|
arch: ['386','amd64']
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: 1.18.x
|
||||||
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
@@ -31,4 +32,4 @@ jobs:
|
|||||||
run: go mod tidy
|
run: go mod tidy
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: 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 ./...
|
||||||
|
|||||||
19
.github/workflows/golang-test-windows.yml
vendored
19
.github/workflows/golang-test-windows.yml
vendored
@@ -1,5 +1,6 @@
|
|||||||
name: Test Code Windows
|
name: Test Code Windows
|
||||||
on: [push,pull_request]
|
on: [push,pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre:
|
pre:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -17,13 +18,8 @@ jobs:
|
|||||||
|
|
||||||
test:
|
test:
|
||||||
needs: pre
|
needs: pre
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go-version: [1.18.x]
|
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: disable defender
|
|
||||||
run: Set-MpPreference -DisableRealtimeMonitoring $true
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@@ -31,27 +27,22 @@ jobs:
|
|||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: 1.18.x
|
||||||
|
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
%LocalAppData%\go-build
|
%LocalAppData%\go-build
|
||||||
~/go/pkg/mod
|
~\go\pkg\mod
|
||||||
|
~\AppData\Local\go-build
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go-
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
- name: enable defender
|
|
||||||
run: Set-MpPreference -DisableRealtimeMonitoring $false
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v2
|
- uses: actions/download-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: syso
|
name: syso
|
||||||
path: iface\
|
path: iface\
|
||||||
|
|
||||||
# - name: Install modules
|
|
||||||
# run: go mod tidy
|
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -tags=load_wgnt_from_rsrc -timeout 5m -p 1 ./...
|
run: go test -tags=load_wgnt_from_rsrc -timeout 5m -p 1 ./...
|
||||||
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 }}" }'
|
||||||
58
.github/workflows/test-docker-compose-linux.yml
vendored
Normal file
58
.github/workflows/test-docker-compose-linux.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
name: Test Docker Compose Linux
|
||||||
|
on: [push,pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
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: cp setup.env
|
||||||
|
run: cp infrastructure_files/tests/setup.env infrastructure_files/
|
||||||
|
|
||||||
|
- name: run configure
|
||||||
|
working-directory: infrastructure_files
|
||||||
|
run: bash -x configure.sh
|
||||||
|
env:
|
||||||
|
CI_NETBIRD_AUTH0_DOMAIN: ${{ secrets.CI_NETBIRD_AUTH0_DOMAIN }}
|
||||||
|
CI_NETBIRD_AUTH0_CLIENT_ID: ${{ secrets.CI_NETBIRD_AUTH0_CLIENT_ID }}
|
||||||
|
CI_NETBIRD_AUTH0_AUDIENCE: testing.ci
|
||||||
|
|
||||||
|
- name: check values
|
||||||
|
working-directory: infrastructure_files
|
||||||
|
env:
|
||||||
|
CI_NETBIRD_AUTH0_DOMAIN: ${{ secrets.CI_NETBIRD_AUTH0_DOMAIN }}
|
||||||
|
CI_NETBIRD_AUTH0_CLIENT_ID: ${{ secrets.CI_NETBIRD_AUTH0_CLIENT_ID }}
|
||||||
|
CI_NETBIRD_AUTH0_AUDIENCE: testing.ci
|
||||||
|
run: |
|
||||||
|
grep AUTH0_DOMAIN docker-compose.yml | grep $CI_NETBIRD_AUTH0_DOMAIN
|
||||||
|
grep AUTH0_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH0_CLIENT_ID
|
||||||
|
grep AUTH0_AUDIENCE docker-compose.yml | grep $CI_NETBIRD_AUTH0_AUDIENCE
|
||||||
|
grep NETBIRD_MGMT_API_ENDPOINT docker-compose.yml | grep "http://localhost:33071"
|
||||||
|
grep NETBIRD_MGMT_GRPC_API_ENDPOINT docker-compose.yml | grep "http://localhost:33073"
|
||||||
|
|
||||||
|
- name: run docker compose up
|
||||||
|
working-directory: infrastructure_files
|
||||||
|
run: |
|
||||||
|
docker-compose up -d
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
- name: test running containers
|
||||||
|
run: |
|
||||||
|
count=$(docker compose ps --format json | jq '.[] | select(.Project | contains("infrastructure_files")) | .State' | grep -c running)
|
||||||
|
test $count -eq 4
|
||||||
|
working-directory: infrastructure_files
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ infrastructure_files/management.json
|
|||||||
infrastructure_files/docker-compose.yml
|
infrastructure_files/docker-compose.yml
|
||||||
*.syso
|
*.syso
|
||||||
client/.distfiles/
|
client/.distfiles/
|
||||||
|
infrastructure_files/setup.env
|
||||||
@@ -13,6 +13,7 @@ builds:
|
|||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
- mips
|
- mips
|
||||||
|
- 386
|
||||||
gomips:
|
gomips:
|
||||||
- hardfloat
|
- hardfloat
|
||||||
- softfloat
|
- softfloat
|
||||||
@@ -21,6 +22,8 @@ builds:
|
|||||||
goarch: arm64
|
goarch: arm64
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm
|
goarch: arm
|
||||||
|
- goos: windows
|
||||||
|
goarch: 386
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w -X github.com/netbirdio/netbird/client/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 }}'
|
||||||
@@ -55,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/
|
||||||
@@ -428,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
|
||||||
@@ -437,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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<strong>:hatching_chick: New release! Seamless Access Controls</strong>.
|
<strong>:hatching_chick: New release! NetBird Easy SSH</strong>.
|
||||||
<a href="https://github.com/netbirdio/netbird/releases/tag/v0.7.0">
|
<a href="https://github.com/netbirdio/netbird/releases/tag/v0.8.0">
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -53,8 +53,10 @@ NetBird creates an overlay peer-to-peer network connecting machines automaticall
|
|||||||
- \[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.
|
||||||
|
|
||||||
**Coming soon:**
|
**Coming soon:**
|
||||||
|
- \[ ] Router nodes
|
||||||
- \[ ] Private DNS.
|
- \[ ] Private DNS.
|
||||||
- \[ ] Mobile clients.
|
- \[ ] Mobile clients.
|
||||||
- \[ ] Network Activity Monitoring.
|
- \[ ] Network Activity Monitoring.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ func init() {
|
|||||||
rootCmd.AddCommand(statusCmd)
|
rootCmd.AddCommand(statusCmd)
|
||||||
rootCmd.AddCommand(loginCmd)
|
rootCmd.AddCommand(loginCmd)
|
||||||
rootCmd.AddCommand(versionCmd)
|
rootCmd.AddCommand(versionCmd)
|
||||||
|
rootCmd.AddCommand(sshCmd)
|
||||||
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service
|
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service
|
||||||
serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service
|
serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service
|
||||||
}
|
}
|
||||||
|
|||||||
115
client/cmd/ssh.go
Normal file
115
client/cmd/ssh.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
nbssh "github.com/netbirdio/netbird/client/ssh"
|
||||||
|
"github.com/netbirdio/netbird/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
port int
|
||||||
|
user = "root"
|
||||||
|
host string
|
||||||
|
)
|
||||||
|
|
||||||
|
var sshCmd = &cobra.Command{
|
||||||
|
Use: "ssh",
|
||||||
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return errors.New("requires a host argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.Split(args[0], "@")
|
||||||
|
if len(split) == 2 {
|
||||||
|
user = split[0]
|
||||||
|
host = split[1]
|
||||||
|
} else {
|
||||||
|
host = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Short: "connect to a remote SSH server",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
|
||||||
|
cmd.SetOut(cmd.OutOrStdout())
|
||||||
|
|
||||||
|
err := util.InitLog(logLevel, "console")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed initializing log %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !util.IsAdmin() {
|
||||||
|
cmd.Printf("error: you must have Administrator privileges to run this command\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := internal.CtxInitState(cmd.Context())
|
||||||
|
|
||||||
|
config, err := internal.ReadConfig("", "", configPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)
|
||||||
|
sshctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// blocking
|
||||||
|
if err := runSSH(sshctx, host, []byte(config.SSHKey), cmd); err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sig:
|
||||||
|
cancel()
|
||||||
|
case <-sshctx.Done():
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSSH(ctx context.Context, addr string, pemKey []byte, cmd *cobra.Command) error {
|
||||||
|
c, err := nbssh.DialWithKey(fmt.Sprintf("%s:%d", addr, port), user, pemKey)
|
||||||
|
if err != nil {
|
||||||
|
cmd.Printf("Error: %v\n", err)
|
||||||
|
cmd.Printf("Couldn't connect. " +
|
||||||
|
"You might be disconnected from the NetBird network, or the NetBird agent isn't running.\n" +
|
||||||
|
"Run the status command: \n\n" +
|
||||||
|
" netbird status\n\n" +
|
||||||
|
"It might also be that the SSH server is disabled on the agent you are trying to connect to.\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
err = c.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = c.OpenTerminal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sshCmd.PersistentFlags().IntVarP(&port, "port", "p", nbssh.DefaultSSHPort, "Sets remote SSH port. Defaults to "+fmt.Sprint(nbssh.DefaultSSHPort))
|
||||||
|
}
|
||||||
@@ -3,13 +3,23 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||||
"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/netip"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
var (
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
detailFlag bool
|
||||||
|
ipsFilter []string
|
||||||
|
statusFilter string
|
||||||
|
ipsFilterMap map[string]struct{}
|
||||||
)
|
)
|
||||||
|
|
||||||
var statusCmd = &cobra.Command{
|
var statusCmd = &cobra.Command{
|
||||||
@@ -20,7 +30,12 @@ var statusCmd = &cobra.Command{
|
|||||||
|
|
||||||
cmd.SetOut(cmd.OutOrStdout())
|
cmd.SetOut(cmd.OutOrStdout())
|
||||||
|
|
||||||
err := util.InitLog(logLevel, "console")
|
err := parseFilters()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.InitLog(logLevel, "console")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed initializing log %v", err)
|
return fmt.Errorf("failed initializing log %v", err)
|
||||||
}
|
}
|
||||||
@@ -35,21 +50,247 @@ var statusCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
resp, err := proto.NewDaemonServiceClient(conn).Status(cmd.Context(), &proto.StatusRequest{})
|
resp, err := proto.NewDaemonServiceClient(conn).Status(cmd.Context(), &proto.StatusRequest{GetFullPeerStatus: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("status failed: %v", status.Convert(err).Message())
|
return fmt.Errorf("status failed: %v", status.Convert(err).Message())
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Printf("Status: %s\n\n", resp.GetStatus())
|
daemonStatus := fmt.Sprintf("Daemon status: %s\n", resp.GetStatus())
|
||||||
if resp.GetStatus() == string(internal.StatusNeedsLogin) || resp.GetStatus() == string(internal.StatusLoginFailed) {
|
if resp.GetStatus() == string(internal.StatusNeedsLogin) || resp.GetStatus() == string(internal.StatusLoginFailed) {
|
||||||
|
|
||||||
cmd.Printf("Run UP command to log in with SSO (interactive login):\n\n" +
|
cmd.Printf("%s\n"+
|
||||||
" netbird up \n\n" +
|
"Run UP command to log in with SSO (interactive login):\n\n"+
|
||||||
"If you are running a self-hosted version and no SSO provider has been configured in your Management Server,\n" +
|
" netbird up \n\n"+
|
||||||
"you can use a setup-key:\n\n netbird up --management-url <YOUR_MANAGEMENT_URL> --setup-key <YOUR_SETUP_KEY>\n\n" +
|
"If you are running a self-hosted version and no SSO provider has been configured in your Management Server,\n"+
|
||||||
"More info: https://www.netbird.io/docs/overview/setup-keys\n\n")
|
"you can use a setup-key:\n\n netbird up --management-url <YOUR_MANAGEMENT_URL> --setup-key <YOUR_SETUP_KEY>\n\n"+
|
||||||
|
"More info: https://www.netbird.io/docs/overview/setup-keys\n\n",
|
||||||
|
daemonStatus,
|
||||||
|
)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pbFullStatus := resp.GetFullStatus()
|
||||||
|
fullStatus := fromProtoFullStatus(pbFullStatus)
|
||||||
|
|
||||||
|
cmd.Print(parseFullStatus(fullStatus, detailFlag, daemonStatus))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ipsFilterMap = make(map[string]struct{})
|
||||||
|
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().StringVar(&statusFilter, "filter-by-status", "", "filters the detailed output by connection status(connected|disconnected), e.g. --filter-by-status connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFilters() error {
|
||||||
|
switch strings.ToLower(statusFilter) {
|
||||||
|
case "", "disconnected", "connected":
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("wrong status filter, should be one of connected|disconnected, got: %s", statusFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ipsFilter) > 0 {
|
||||||
|
for _, addr := range ipsFilter {
|
||||||
|
_, err := netip.ParseAddr(addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got an invalid IP address in the filter: address %s, error %s", addr, err)
|
||||||
|
}
|
||||||
|
ipsFilterMap[addr] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromProtoFullStatus(pbFullStatus *proto.FullStatus) nbStatus.FullStatus {
|
||||||
|
var fullStatus nbStatus.FullStatus
|
||||||
|
managementState := pbFullStatus.GetManagementState()
|
||||||
|
fullStatus.ManagementState.URL = managementState.GetURL()
|
||||||
|
fullStatus.ManagementState.Connected = managementState.GetConnected()
|
||||||
|
|
||||||
|
signalState := pbFullStatus.GetSignalState()
|
||||||
|
fullStatus.SignalState.URL = signalState.GetURL()
|
||||||
|
fullStatus.SignalState.Connected = signalState.GetConnected()
|
||||||
|
|
||||||
|
localPeerState := pbFullStatus.GetLocalPeerState()
|
||||||
|
fullStatus.LocalPeerState.IP = localPeerState.GetIP()
|
||||||
|
fullStatus.LocalPeerState.PubKey = localPeerState.GetPubKey()
|
||||||
|
fullStatus.LocalPeerState.KernelInterface = localPeerState.GetKernelInterface()
|
||||||
|
|
||||||
|
var peersState []nbStatus.PeerState
|
||||||
|
|
||||||
|
for _, pbPeerState := range pbFullStatus.GetPeers() {
|
||||||
|
timeLocal := pbPeerState.GetConnStatusUpdate().AsTime().Local()
|
||||||
|
peerState := nbStatus.PeerState{
|
||||||
|
IP: pbPeerState.GetIP(),
|
||||||
|
PubKey: pbPeerState.GetPubKey(),
|
||||||
|
ConnStatus: pbPeerState.GetConnStatus(),
|
||||||
|
ConnStatusUpdate: timeLocal,
|
||||||
|
Relayed: pbPeerState.GetRelayed(),
|
||||||
|
Direct: pbPeerState.GetDirect(),
|
||||||
|
LocalIceCandidateType: pbPeerState.GetLocalIceCandidateType(),
|
||||||
|
RemoteIceCandidateType: pbPeerState.GetRemoteIceCandidateType(),
|
||||||
|
}
|
||||||
|
peersState = append(peersState, peerState)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullStatus.Peers = peersState
|
||||||
|
|
||||||
|
return fullStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonStatus string) string {
|
||||||
|
var (
|
||||||
|
managementStatusURL = ""
|
||||||
|
signalStatusURL = ""
|
||||||
|
managementConnString = "Disconnected"
|
||||||
|
signalConnString = "Disconnected"
|
||||||
|
interfaceTypeString = "Userspace"
|
||||||
|
)
|
||||||
|
|
||||||
|
if printDetail {
|
||||||
|
managementStatusURL = fmt.Sprintf(" to %s", fullStatus.ManagementState.URL)
|
||||||
|
signalStatusURL = fmt.Sprintf(" to %s", fullStatus.SignalState.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullStatus.ManagementState.Connected {
|
||||||
|
managementConnString = "Connected"
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullStatus.SignalState.Connected {
|
||||||
|
signalConnString = "Connected"
|
||||||
|
}
|
||||||
|
|
||||||
|
interfaceIP := fullStatus.LocalPeerState.IP
|
||||||
|
|
||||||
|
if fullStatus.LocalPeerState.KernelInterface {
|
||||||
|
interfaceTypeString = "Kernel"
|
||||||
|
} else if fullStatus.LocalPeerState.IP == "" {
|
||||||
|
interfaceTypeString = "N/A"
|
||||||
|
interfaceIP = "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedPeersString, peersConnected := parsePeers(fullStatus.Peers, printDetail)
|
||||||
|
|
||||||
|
peersCountString := fmt.Sprintf("%d/%d Connected", peersConnected, len(fullStatus.Peers))
|
||||||
|
|
||||||
|
summary := fmt.Sprintf(
|
||||||
|
"%s"+ // daemon status
|
||||||
|
"Management: %s%s\n"+
|
||||||
|
"Signal: %s%s\n"+
|
||||||
|
"NetBird IP: %s\n"+
|
||||||
|
"Interface type: %s\n"+
|
||||||
|
"Peers count: %s\n",
|
||||||
|
daemonStatus,
|
||||||
|
managementConnString,
|
||||||
|
managementStatusURL,
|
||||||
|
signalConnString,
|
||||||
|
signalStatusURL,
|
||||||
|
interfaceIP,
|
||||||
|
interfaceTypeString,
|
||||||
|
peersCountString,
|
||||||
|
)
|
||||||
|
|
||||||
|
if printDetail {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Peers detail:"+
|
||||||
|
"%s\n"+
|
||||||
|
"%s",
|
||||||
|
parsedPeersString,
|
||||||
|
summary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return summary
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePeers(peers []nbStatus.PeerState, printDetail bool) (string, int) {
|
||||||
|
var (
|
||||||
|
peersString = ""
|
||||||
|
peersConnected = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(peers) > 0 {
|
||||||
|
sort.SliceStable(peers, func(i, j int) bool {
|
||||||
|
iAddr, _ := netip.ParseAddr(peers[i].IP)
|
||||||
|
jAddr, _ := netip.ParseAddr(peers[j].IP)
|
||||||
|
return iAddr.Compare(jAddr) == -1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedStatusString := peer.StatusConnected.String()
|
||||||
|
|
||||||
|
for _, peerState := range peers {
|
||||||
|
peerConnectionStatus := false
|
||||||
|
if peerState.ConnStatus == connectedStatusString {
|
||||||
|
peersConnected = peersConnected + 1
|
||||||
|
peerConnectionStatus = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if printDetail {
|
||||||
|
|
||||||
|
if skipDetailByFilters(peerState, peerConnectionStatus) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
localICE := "-"
|
||||||
|
remoteICE := "-"
|
||||||
|
connType := "-"
|
||||||
|
|
||||||
|
if peerConnectionStatus {
|
||||||
|
localICE = peerState.LocalIceCandidateType
|
||||||
|
remoteICE = peerState.RemoteIceCandidateType
|
||||||
|
connType = "P2P"
|
||||||
|
if peerState.Relayed {
|
||||||
|
connType = "Relayed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
peerString := fmt.Sprintf(
|
||||||
|
"\n Peer:\n"+
|
||||||
|
" NetBird IP: %s\n"+
|
||||||
|
" Public key: %s\n"+
|
||||||
|
" Status: %s\n"+
|
||||||
|
" -- detail --\n"+
|
||||||
|
" Connection type: %s\n"+
|
||||||
|
" Direct: %t\n"+
|
||||||
|
" ICE candidate (Local/Remote): %s/%s\n"+
|
||||||
|
" Last connection update: %s\n",
|
||||||
|
peerState.IP,
|
||||||
|
peerState.PubKey,
|
||||||
|
peerState.ConnStatus,
|
||||||
|
connType,
|
||||||
|
peerState.Direct,
|
||||||
|
localICE,
|
||||||
|
remoteICE,
|
||||||
|
peerState.ConnStatusUpdate.Format("2006-01-02 15:04:05"),
|
||||||
|
)
|
||||||
|
|
||||||
|
peersString = peersString + peerString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return peersString, peersConnected
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipDetailByFilters(peerState nbStatus.PeerState, isConnected bool) bool {
|
||||||
|
statusEval := false
|
||||||
|
ipEval := false
|
||||||
|
|
||||||
|
if statusFilter != "" {
|
||||||
|
lowerStatusFilter := strings.ToLower(statusFilter)
|
||||||
|
if lowerStatusFilter == "disconnected" && isConnected {
|
||||||
|
statusEval = true
|
||||||
|
} else if lowerStatusFilter == "connected" && !isConnected {
|
||||||
|
statusEval = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ipsFilter) > 0 {
|
||||||
|
_, ok := ipsFilterMap[peerState.IP]
|
||||||
|
if !ok {
|
||||||
|
ipEval = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return statusEval || ipEval
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -39,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)
|
||||||
@@ -47,7 +50,7 @@ var upCmd = &cobra.Command{
|
|||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
ctx, cancel = context.WithCancel(ctx)
|
ctx, cancel = context.WithCancel(ctx)
|
||||||
SetupCloseHandler(ctx, cancel)
|
SetupCloseHandler(ctx, cancel)
|
||||||
return internal.RunClient(ctx, config)
|
return internal.RunClient(ctx, config, nbStatus.NewRecorder())
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/ssh"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
mgm "github.com/netbirdio/netbird/management/client"
|
mgm "github.com/netbirdio/netbird/management/client"
|
||||||
|
"github.com/netbirdio/netbird/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/iface"
|
|
||||||
"github.com/netbirdio/netbird/util"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var managementURLDefault *url.URL
|
var managementURLDefault *url.URL
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -38,14 +38,20 @@ type Config struct {
|
|||||||
AdminURL *url.URL
|
AdminURL *url.URL
|
||||||
WgIface string
|
WgIface string
|
||||||
IFaceBlackList []string
|
IFaceBlackList []string
|
||||||
|
// SSHKey is a private SSH key in a PEM format
|
||||||
|
SSHKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
// createNewConfig creates a new config generating a new Wireguard key and saving to file
|
// createNewConfig creates a new config generating a new Wireguard key and saving to file
|
||||||
func createNewConfig(managementURL, adminURL, configPath, preSharedKey string) (*Config, error) {
|
func createNewConfig(managementURL, adminURL, configPath, preSharedKey string) (*Config, error) {
|
||||||
wgKey := generateKey()
|
wgKey := generateKey()
|
||||||
config := &Config{PrivateKey: wgKey, WgIface: iface.WgInterfaceDefault, IFaceBlackList: []string{}}
|
pem, err := ssh.GeneratePrivateKey(ssh.ED25519)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config := &Config{SSHKey: string(pem), PrivateKey: wgKey, WgIface: iface.WgInterfaceDefault, 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
|
||||||
}
|
}
|
||||||
@@ -58,10 +64,18 @@ func createNewConfig(managementURL, adminURL, configPath, preSharedKey string) (
|
|||||||
config.PreSharedKey = preSharedKey
|
config.PreSharedKey = preSharedKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if adminURL != "" {
|
||||||
|
newURL, err := ParseURL("Admin Panel URL", adminURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.AdminURL = newURL
|
||||||
|
}
|
||||||
|
|
||||||
config.IFaceBlackList = []string{iface.WgInterfaceDefault, "tun0", "zt", "ZeroTier", "utun", "wg", "ts",
|
config.IFaceBlackList = []string{iface.WgInterfaceDefault, "tun0", "zt", "ZeroTier", "utun", "wg", "ts",
|
||||||
"Tailscale", "tailscale"}
|
"Tailscale", "tailscale"}
|
||||||
|
|
||||||
err := util.WriteJson(configPath, config)
|
err = util.WriteJson(configPath, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -69,7 +83,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())
|
||||||
@@ -101,7 +116,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
|
||||||
}
|
}
|
||||||
@@ -112,7 +127,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
|
||||||
}
|
}
|
||||||
@@ -126,6 +141,14 @@ func ReadConfig(managementURL, adminURL, configPath string, preSharedKey *string
|
|||||||
config.PreSharedKey = *preSharedKey
|
config.PreSharedKey = *preSharedKey
|
||||||
refresh = true
|
refresh = true
|
||||||
}
|
}
|
||||||
|
if config.SSHKey == "" {
|
||||||
|
pem, err := ssh.GeneratePrivateKey(ssh.ED25519)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.SSHKey = string(pem)
|
||||||
|
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
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/ssh"
|
||||||
|
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||||
|
mgmtcmd "github.com/netbirdio/netbird/management/cmd"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
@@ -15,17 +20,17 @@ import (
|
|||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
gstatus "google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunClient with main logic.
|
// RunClient with main logic.
|
||||||
func RunClient(ctx context.Context, config *Config) error {
|
func RunClient(ctx context.Context, config *Config, statusRecorder *nbStatus.Status) error {
|
||||||
backOff := &backoff.ExponentialBackOff{
|
backOff := &backoff.ExponentialBackOff{
|
||||||
InitialInterval: time.Second,
|
InitialInterval: time.Second,
|
||||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
RandomizationFactor: 1,
|
||||||
Multiplier: backoff.DefaultMultiplier,
|
Multiplier: 1.7,
|
||||||
MaxInterval: 10 * time.Second,
|
MaxInterval: 15 * time.Second,
|
||||||
MaxElapsedTime: 24 * 3 * time.Hour, // stop the client after 3 days trying (must be a huge problem, e.g permission denied)
|
MaxElapsedTime: 3 * 30 * 24 * time.Hour, // 3 months
|
||||||
Stop: backoff.Stop,
|
Stop: backoff.Stop,
|
||||||
Clock: backoff.SystemClock,
|
Clock: backoff.SystemClock,
|
||||||
}
|
}
|
||||||
@@ -39,6 +44,25 @@ func RunClient(ctx context.Context, config *Config) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
wrapErr := state.Wrap
|
wrapErr := state.Wrap
|
||||||
|
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mgmTlsEnabled bool
|
||||||
|
if config.ManagementURL.Scheme == "https" {
|
||||||
|
mgmTlsEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
publicSSHKey, err := ssh.GeneratePublicKey([]byte(config.SSHKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
managementURL := config.ManagementURL.String()
|
||||||
|
statusRecorder.MarkManagementDisconnected(managementURL)
|
||||||
|
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
// if context cancelled we not start new backoff cycle
|
// if context cancelled we not start new backoff cycle
|
||||||
select {
|
select {
|
||||||
@@ -48,32 +72,42 @@ func RunClient(ctx context.Context, config *Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.Set(StatusConnecting)
|
state.Set(StatusConnecting)
|
||||||
// validate our peer's Wireguard PRIVATE key
|
|
||||||
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
|
||||||
return wrapErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var mgmTlsEnabled bool
|
|
||||||
if config.ManagementURL.Scheme == "https" {
|
|
||||||
mgmTlsEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
engineCtx, cancel := context.WithCancel(ctx)
|
engineCtx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer func() {
|
||||||
|
statusRecorder.MarkManagementDisconnected(managementURL)
|
||||||
|
statusRecorder.CleanLocalPeerState()
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
// 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)
|
mgmClient, loginResp, err := connectToManagement(engineCtx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled,
|
||||||
|
publicSSHKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug(err)
|
log.Debug(err)
|
||||||
if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.PermissionDenied) {
|
||||||
log.Info("peer registration required. Please run `netbird status` for details")
|
|
||||||
state.Set(StatusNeedsLogin)
|
state.Set(StatusNeedsLogin)
|
||||||
return nil
|
return backoff.Permanent(wrapErr(err)) // unrecoverable error
|
||||||
}
|
}
|
||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
|
statusRecorder.MarkManagementConnected(managementURL)
|
||||||
|
|
||||||
|
localPeerState := nbStatus.LocalPeerState{
|
||||||
|
IP: loginResp.GetPeerConfig().GetAddress(),
|
||||||
|
PubKey: myPrivateKey.PublicKey().String(),
|
||||||
|
KernelInterface: iface.WireguardModExists(),
|
||||||
|
}
|
||||||
|
|
||||||
|
statusRecorder.UpdateLocalPeerState(localPeerState)
|
||||||
|
|
||||||
|
signalURL := fmt.Sprintf("%s://%s",
|
||||||
|
strings.ToLower(loginResp.GetWiretrusteeConfig().GetSignal().GetProtocol().String()),
|
||||||
|
loginResp.GetWiretrusteeConfig().GetSignal().GetUri(),
|
||||||
|
)
|
||||||
|
|
||||||
|
statusRecorder.MarkSignalDisconnected(signalURL)
|
||||||
|
defer statusRecorder.MarkSignalDisconnected(signalURL)
|
||||||
|
|
||||||
// with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
|
// with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
|
||||||
signalClient, err := connectToSignal(engineCtx, loginResp.GetWiretrusteeConfig(), myPrivateKey)
|
signalClient, err := connectToSignal(engineCtx, loginResp.GetWiretrusteeConfig(), myPrivateKey)
|
||||||
@@ -82,6 +116,8 @@ func RunClient(ctx context.Context, config *Config) error {
|
|||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statusRecorder.MarkSignalConnected(signalURL)
|
||||||
|
|
||||||
peerConfig := loginResp.GetPeerConfig()
|
peerConfig := loginResp.GetPeerConfig()
|
||||||
|
|
||||||
engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig)
|
engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig)
|
||||||
@@ -90,7 +126,7 @@ func RunClient(ctx context.Context, config *Config) error {
|
|||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
engine := NewEngine(engineCtx, cancel, signalClient, mgmClient, engineConfig)
|
engine := NewEngine(engineCtx, cancel, signalClient, mgmClient, engineConfig, statusRecorder)
|
||||||
err = engine.Start()
|
err = engine.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error while starting Netbird Connection Engine: %s", err)
|
log.Errorf("error while starting Netbird Connection Engine: %s", err)
|
||||||
@@ -109,6 +145,7 @@ func RunClient(ctx context.Context, config *Config) error {
|
|||||||
log.Errorf("failed closing Management Service client %v", err)
|
log.Errorf("failed closing Management Service client %v", err)
|
||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = signalClient.Close()
|
err = signalClient.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed closing Signal Service client %v", err)
|
log.Errorf("failed closing Signal Service client %v", err)
|
||||||
@@ -121,7 +158,7 @@ func RunClient(ctx context.Context, config *Config) error {
|
|||||||
return wrapErr(err)
|
return wrapErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("stopped Netbird client")
|
log.Info("stopped NetBird client")
|
||||||
|
|
||||||
if _, err := state.Status(); err == ErrResetConnection {
|
if _, err := state.Status(); err == ErrResetConnection {
|
||||||
return err
|
return err
|
||||||
@@ -130,9 +167,9 @@ func RunClient(ctx context.Context, config *Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := backoff.Retry(operation, backOff)
|
err = backoff.Retry(operation, backOff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("exiting client retry loop due to unrecoverable error: %s", err)
|
log.Debugf("exiting client retry loop due to unrecoverable error: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -147,6 +184,7 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe
|
|||||||
IFaceBlackList: config.IFaceBlackList,
|
IFaceBlackList: config.IFaceBlackList,
|
||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: iface.DefaultWgPort,
|
WgPort: iface.DefaultWgPort,
|
||||||
|
SSHKey: []byte(config.SSHKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.PreSharedKey != "" {
|
if config.PreSharedKey != "" {
|
||||||
@@ -172,28 +210,28 @@ func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig,
|
|||||||
signalClient, err := signal.NewClient(ctx, wtConfig.Signal.Uri, ourPrivateKey, sigTLSEnabled)
|
signalClient, err := signal.NewClient(ctx, wtConfig.Signal.Uri, ourPrivateKey, sigTLSEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error while connecting to the Signal Exchange Service %s: %s", wtConfig.Signal.Uri, err)
|
log.Errorf("error while connecting to the Signal Exchange Service %s: %s", wtConfig.Signal.Uri, err)
|
||||||
return nil, status.Errorf(codes.FailedPrecondition, "failed connecting to Signal Service : %s", err)
|
return nil, gstatus.Errorf(codes.FailedPrecondition, "failed connecting to Signal Service : %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
// connectToManagement 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) (*mgm.GrpcClient, *mgmProto.LoginResponse, error) {
|
func connectToManagement(ctx context.Context, managementAddr string, ourPrivateKey wgtypes.Key, tlsEnabled bool, pubSSHKey []byte) (*mgm.GrpcClient, *mgmProto.LoginResponse, error) {
|
||||||
log.Debugf("connecting to Management Service %s", managementAddr)
|
log.Debugf("connecting to Management Service %s", managementAddr)
|
||||||
client, err := mgm.NewClient(ctx, managementAddr, ourPrivateKey, tlsEnabled)
|
client, err := mgm.NewClient(ctx, managementAddr, ourPrivateKey, tlsEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, status.Errorf(codes.FailedPrecondition, "failed connecting to Management Service : %s", err)
|
return nil, nil, gstatus.Errorf(codes.FailedPrecondition, "failed connecting to Management Service : %s", err)
|
||||||
}
|
}
|
||||||
log.Debugf("connected to management server %s", managementAddr)
|
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, status.Errorf(codes.FailedPrecondition, "failed while getting Management Service public key: %s", err)
|
return nil, 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)
|
loginResp, err := client.Login(*serverPublicKey, sysInfo, pubSSHKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -202,3 +240,67 @@ func connectToManagement(ctx context.Context, managementAddr string, ourPrivateK
|
|||||||
|
|
||||||
return client, loginResp, nil
|
return client, loginResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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", mgmtcmd.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 client.Close() //nolint
|
||||||
|
|
||||||
|
// 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,12 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
nbssh "github.com/netbirdio/netbird/client/ssh"
|
||||||
|
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -54,6 +58,9 @@ type EngineConfig struct {
|
|||||||
|
|
||||||
// UDPMuxSrflxPort default value 0 - the system will pick an available port
|
// UDPMuxSrflxPort default value 0 - the system will pick an available port
|
||||||
UDPMuxSrflxPort int
|
UDPMuxSrflxPort int
|
||||||
|
|
||||||
|
// SSHKey is a private SSH key in a PEM format
|
||||||
|
SSHKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
|
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
|
||||||
@@ -87,6 +94,11 @@ type Engine struct {
|
|||||||
|
|
||||||
// networkSerial is the latest CurrentSerial (state ID) of the network sent by the Management service
|
// networkSerial is the latest CurrentSerial (state ID) of the network sent by the Management service
|
||||||
networkSerial uint64
|
networkSerial uint64
|
||||||
|
|
||||||
|
sshServerFunc func(hostKeyPEM []byte, addr string) (nbssh.Server, error)
|
||||||
|
sshServer nbssh.Server
|
||||||
|
|
||||||
|
statusRecorder *nbstatus.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer is an instance of the Connection Peer
|
// Peer is an instance of the Connection Peer
|
||||||
@@ -98,19 +110,22 @@ type Peer struct {
|
|||||||
// NewEngine creates a new Connection Engine
|
// NewEngine creates a new Connection Engine
|
||||||
func NewEngine(
|
func NewEngine(
|
||||||
ctx context.Context, cancel context.CancelFunc,
|
ctx context.Context, cancel context.CancelFunc,
|
||||||
signalClient signal.Client, mgmClient mgm.Client, config *EngineConfig,
|
signalClient signal.Client, mgmClient mgm.Client,
|
||||||
|
config *EngineConfig, statusRecorder *nbstatus.Status,
|
||||||
) *Engine {
|
) *Engine {
|
||||||
return &Engine{
|
return &Engine{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
signal: signalClient,
|
signal: signalClient,
|
||||||
mgmClient: mgmClient,
|
mgmClient: mgmClient,
|
||||||
peerConns: map[string]*peer.Conn{},
|
peerConns: map[string]*peer.Conn{},
|
||||||
syncMsgMux: &sync.Mutex{},
|
syncMsgMux: &sync.Mutex{},
|
||||||
config: config,
|
config: config,
|
||||||
STUNs: []*ice.URL{},
|
STUNs: []*ice.URL{},
|
||||||
TURNs: []*ice.URL{},
|
TURNs: []*ice.URL{},
|
||||||
networkSerial: 0,
|
networkSerial: 0,
|
||||||
|
sshServerFunc: nbssh.DefaultSSHServer,
|
||||||
|
statusRecorder: statusRecorder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +175,13 @@ func (e *Engine) Stop() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isNil(e.sshServer) {
|
||||||
|
err := e.sshServer.Stop()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed stopping the SSH server: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Infof("stopped Netbird Engine")
|
log.Infof("stopped Netbird Engine")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -283,9 +305,21 @@ func (e *Engine) removeAllPeers() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// removePeer closes an existing peer connection and removes a peer
|
// removePeer closes an existing peer connection, removes a peer, and clears authorized key of the SSH server
|
||||||
func (e *Engine) removePeer(peerKey string) error {
|
func (e *Engine) removePeer(peerKey string) error {
|
||||||
log.Debugf("removing peer from engine %s", peerKey)
|
log.Debugf("removing peer from engine %s", peerKey)
|
||||||
|
|
||||||
|
if !isNil(e.sshServer) {
|
||||||
|
e.sshServer.RemoveAuthorizedKey(peerKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := e.statusRecorder.RemovePeer(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("received error when removing peer %s from status recorder: %v", peerKey, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
conn, exists := e.peerConns[peerKey]
|
conn, exists := e.peerConns[peerKey]
|
||||||
if exists {
|
if exists {
|
||||||
delete(e.peerConns, peerKey)
|
delete(e.peerConns, peerKey)
|
||||||
@@ -398,12 +432,6 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if update.GetNetworkMap() != nil {
|
if update.GetNetworkMap() != nil {
|
||||||
if update.GetNetworkMap().GetPeerConfig() != nil {
|
|
||||||
err := e.updateConfig(update.GetNetworkMap().GetPeerConfig())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// only apply new changes and ignore old ones
|
// only apply new changes and ignore old ones
|
||||||
err := e.updateNetworkMap(update.GetNetworkMap())
|
err := e.updateNetworkMap(update.GetNetworkMap())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -414,6 +442,53 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isNil(server nbssh.Server) bool {
|
||||||
|
return server == nil || reflect.ValueOf(server).IsNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Engine) updateSSH(sshConf *mgmProto.SSHConfig) error {
|
||||||
|
if sshConf.GetSshEnabled() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
log.Warnf("running SSH server on Windows is not supported")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// start SSH server if it wasn't running
|
||||||
|
if isNil(e.sshServer) {
|
||||||
|
//nil sshServer means it has not yet been started
|
||||||
|
var err error
|
||||||
|
e.sshServer, err = e.sshServerFunc(e.config.SSHKey,
|
||||||
|
fmt.Sprintf("%s:%d", e.wgInterface.Address.IP.String(), nbssh.DefaultSSHPort))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
// blocking
|
||||||
|
err = e.sshServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
// will throw error when we stop it even if it is a graceful stop
|
||||||
|
log.Debugf("stopped SSH server with error %v", err)
|
||||||
|
}
|
||||||
|
e.syncMsgMux.Lock()
|
||||||
|
defer e.syncMsgMux.Unlock()
|
||||||
|
e.sshServer = nil
|
||||||
|
log.Infof("stopped SSH server")
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
log.Debugf("SSH server is already running")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Disable SSH server request, so stop it if it was running
|
||||||
|
if !isNil(e.sshServer) {
|
||||||
|
err := e.sshServer.Stop()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to stop SSH server %v", err)
|
||||||
|
}
|
||||||
|
e.sshServer = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
|
func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
|
||||||
if e.wgInterface.Address.String() != conf.Address {
|
if e.wgInterface.Address.String() != conf.Address {
|
||||||
oldAddr := e.wgInterface.Address.String()
|
oldAddr := e.wgInterface.Address.String()
|
||||||
@@ -422,9 +497,17 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
e.config.WgAddr = conf.Address
|
||||||
log.Infof("updated peer address from %s to %s", oldAddr, conf.Address)
|
log.Infof("updated peer address from %s to %s", oldAddr, conf.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.GetSshConfig() != nil {
|
||||||
|
err := e.updateSSH(conf.GetSshConfig())
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed handling SSH server setup %v", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,6 +569,15 @@ func (e *Engine) updateTURNs(turns []*mgmProto.ProtectedHostConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
||||||
|
|
||||||
|
// intentionally leave it before checking serial because for now it can happen that peer IP changed but serial didn't
|
||||||
|
if networkMap.GetPeerConfig() != nil {
|
||||||
|
err := e.updateConfig(networkMap.GetPeerConfig())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serial := networkMap.GetSerial()
|
serial := networkMap.GetSerial()
|
||||||
if e.networkSerial > serial {
|
if e.networkSerial > serial {
|
||||||
log.Debugf("received outdated NetworkMap with serial %d, ignoring", serial)
|
log.Debugf("received outdated NetworkMap with serial %d, ignoring", serial)
|
||||||
@@ -515,6 +607,18 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update SSHServer by adding remote peer SSH keys
|
||||||
|
if !isNil(e.sshServer) {
|
||||||
|
for _, config := range networkMap.GetRemotePeers() {
|
||||||
|
if config.GetSshConfig() != nil && config.GetSshConfig().GetSshPubKey() != nil {
|
||||||
|
err := e.sshServer.AddAuthorizedKey(config.WgPubKey, string(config.GetSshConfig().GetSshPubKey()))
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed adding authroized key to SSH DefaultServer %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e.networkSerial = serial
|
e.networkSerial = serial
|
||||||
@@ -543,12 +647,17 @@ func (e *Engine) addNewPeer(peerConfig *mgmProto.RemotePeerConfig) error {
|
|||||||
}
|
}
|
||||||
e.peerConns[peerKey] = conn
|
e.peerConns[peerKey] = conn
|
||||||
|
|
||||||
|
err = e.statusRecorder.AddPeer(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("error adding peer %s to status recorder, got error: %v", peerKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
go e.connWorker(conn, peerKey)
|
go e.connWorker(conn, peerKey)
|
||||||
}
|
}
|
||||||
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
|
||||||
@@ -567,6 +676,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)
|
||||||
@@ -613,7 +729,7 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
|
|||||||
ProxyConfig: proxyConfig,
|
ProxyConfig: proxyConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
peerConn, err := peer.NewConn(config)
|
peerConn, err := peer.NewConn(config, e.statusRecorder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/ssh"
|
||||||
|
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -40,6 +44,140 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestEngine_SSH(t *testing.T) {
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("skipping TestEngine_SSH on Windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||||
|
WgIfaceName: "utun101",
|
||||||
|
WgAddr: "100.64.0.1/24",
|
||||||
|
WgPrivateKey: key,
|
||||||
|
WgPort: 33100,
|
||||||
|
}, nbstatus.NewRecorder())
|
||||||
|
|
||||||
|
var sshKeysAdded []string
|
||||||
|
var sshPeersRemoved []string
|
||||||
|
|
||||||
|
sshCtx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
engine.sshServerFunc = func(hostKeyPEM []byte, addr string) (ssh.Server, error) {
|
||||||
|
return &ssh.MockServer{
|
||||||
|
Ctx: sshCtx,
|
||||||
|
StopFunc: func() error {
|
||||||
|
cancel()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
StartFunc: func() error {
|
||||||
|
<-ctx.Done()
|
||||||
|
return ctx.Err()
|
||||||
|
},
|
||||||
|
AddAuthorizedKeyFunc: func(peer, newKey string) error {
|
||||||
|
sshKeysAdded = append(sshKeysAdded, newKey)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RemoveAuthorizedKeyFunc: func(peer string) {
|
||||||
|
sshPeersRemoved = append(sshPeersRemoved, peer)
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
err = engine.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := engine.Stop()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
peerWithSSH := &mgmtProto.RemotePeerConfig{
|
||||||
|
WgPubKey: "MNHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||||
|
AllowedIps: []string{"100.64.0.21/24"},
|
||||||
|
SshConfig: &mgmtProto.SSHConfig{
|
||||||
|
SshPubKey: []byte("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFATYCqaQw/9id1Qkq3n16JYhDhXraI6Pc1fgB8ynEfQ"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSH server is not enabled so SSH config of a remote peer should be ignored
|
||||||
|
networkMap := &mgmtProto.NetworkMap{
|
||||||
|
Serial: 6,
|
||||||
|
PeerConfig: nil,
|
||||||
|
RemotePeers: []*mgmtProto.RemotePeerConfig{peerWithSSH},
|
||||||
|
RemotePeersIsEmpty: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = engine.updateNetworkMap(networkMap)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Nil(t, engine.sshServer)
|
||||||
|
|
||||||
|
// SSH server is enabled, therefore SSH config should be applied
|
||||||
|
networkMap = &mgmtProto.NetworkMap{
|
||||||
|
Serial: 7,
|
||||||
|
PeerConfig: &mgmtProto.PeerConfig{Address: "100.64.0.1/24",
|
||||||
|
SshConfig: &mgmtProto.SSHConfig{SshEnabled: true}},
|
||||||
|
RemotePeers: []*mgmtProto.RemotePeerConfig{peerWithSSH},
|
||||||
|
RemotePeersIsEmpty: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = engine.updateNetworkMap(networkMap)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
assert.NotNil(t, engine.sshServer)
|
||||||
|
assert.Contains(t, sshKeysAdded, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFATYCqaQw/9id1Qkq3n16JYhDhXraI6Pc1fgB8ynEfQ")
|
||||||
|
|
||||||
|
// now remove peer
|
||||||
|
networkMap = &mgmtProto.NetworkMap{
|
||||||
|
Serial: 8,
|
||||||
|
RemotePeers: []*mgmtProto.RemotePeerConfig{},
|
||||||
|
RemotePeersIsEmpty: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = engine.updateNetworkMap(networkMap)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//time.Sleep(250 * time.Millisecond)
|
||||||
|
assert.NotNil(t, engine.sshServer)
|
||||||
|
assert.Contains(t, sshPeersRemoved, "MNHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=")
|
||||||
|
|
||||||
|
// now disable SSH server
|
||||||
|
networkMap = &mgmtProto.NetworkMap{
|
||||||
|
Serial: 9,
|
||||||
|
PeerConfig: &mgmtProto.PeerConfig{Address: "100.64.0.1/24",
|
||||||
|
SshConfig: &mgmtProto.SSHConfig{SshEnabled: false}},
|
||||||
|
RemotePeers: []*mgmtProto.RemotePeerConfig{peerWithSSH},
|
||||||
|
RemotePeersIsEmpty: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = engine.updateNetworkMap(networkMap)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Nil(t, engine.sshServer)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestEngine_UpdateNetworkMap(t *testing.T) {
|
func TestEngine_UpdateNetworkMap(t *testing.T) {
|
||||||
// test setup
|
// test setup
|
||||||
key, err := wgtypes.GeneratePrivateKey()
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
@@ -52,11 +190,12 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||||
WgIfaceName: "utun100",
|
WgIfaceName: "utun102",
|
||||||
WgAddr: "100.64.0.1/24",
|
WgAddr: "100.64.0.1/24",
|
||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
})
|
}, nbstatus.NewRecorder())
|
||||||
|
engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", iface.DefaultMTU)
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
@@ -231,11 +370,11 @@ func TestEngine_Sync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, &EngineConfig{
|
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, &EngineConfig{
|
||||||
WgIfaceName: "utun100",
|
WgIfaceName: "utun103",
|
||||||
WgAddr: "100.64.0.1/24",
|
WgAddr: "100.64.0.1/24",
|
||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: 33100,
|
WgPort: 33100,
|
||||||
})
|
}, nbstatus.NewRecorder())
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
err := engine.Stop()
|
err := engine.Stop()
|
||||||
@@ -418,7 +557,7 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
info := system.GetInfo(ctx)
|
info := system.GetInfo(ctx)
|
||||||
resp, err := mgmtClient.Register(*publicKey, setupKey, "", info)
|
resp, err := mgmtClient.Register(*publicKey, setupKey, "", info, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -438,7 +577,7 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
|
|||||||
WgPort: wgPort,
|
WgPort: wgPort,
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewEngine(ctx, cancel, signalClient, mgmtClient, conf), nil
|
return NewEngine(ctx, cancel, signalClient, mgmtClient, conf, nbstatus.NewRecorder()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startSignal(port int) (*grpc.Server, error) {
|
func startSignal(port int) (*grpc.Server, error) {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/netbirdio/netbird/client/ssh"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
mgm "github.com/netbirdio/netbird/management/client"
|
mgm "github.com/netbirdio/netbird/management/client"
|
||||||
mgmProto "github.com/netbirdio/netbird/management/proto"
|
mgmProto "github.com/netbirdio/netbird/management/proto"
|
||||||
@@ -40,11 +40,16 @@ func Login(ctx context.Context, config *Config, setupKey string, jwtToken string
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = loginPeer(ctx, *serverKey, mgmClient, setupKey, jwtToken)
|
pubSSHKey, err := ssh.GeneratePublicKey([]byte(config.SSHKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = loginPeer(ctx, *serverKey, mgmClient, setupKey, jwtToken, pubSSHKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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 {
|
||||||
@@ -56,26 +61,24 @@ func Login(ctx context.Context, config *Config, setupKey string, jwtToken string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// loginPeer attempts to login to Management Service. If peer wasn't registered, tries the registration flow.
|
// loginPeer attempts to login to Management Service. If peer wasn't registered, tries the registration flow.
|
||||||
func loginPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string, jwtToken string) (*mgmProto.LoginResponse, error) {
|
func loginPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string, jwtToken string, pubSSHKey []byte) (*mgmProto.LoginResponse, error) {
|
||||||
sysInfo := system.GetInfo(ctx)
|
sysInfo := system.GetInfo(ctx)
|
||||||
loginResp, err := client.Login(serverPublicKey, sysInfo)
|
loginResp, err := client.Login(serverPublicKey, sysInfo, pubSSHKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
||||||
log.Debugf("peer registration required")
|
log.Debugf("peer registration required")
|
||||||
return registerPeer(ctx, serverPublicKey, client, setupKey, jwtToken)
|
return registerPeer(ctx, serverPublicKey, client, setupKey, jwtToken, pubSSHKey)
|
||||||
} else {
|
} else {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("peer has successfully logged-in to Management Service")
|
|
||||||
|
|
||||||
return loginResp, nil
|
return loginResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerPeer checks whether setupKey was provided via cmd line and if not then it prompts user to enter a key.
|
// registerPeer checks whether setupKey was provided via cmd line and if not then it prompts user to enter a key.
|
||||||
// Otherwise tries to register with the provided setupKey via command line.
|
// Otherwise tries to register with the provided setupKey via command line.
|
||||||
func registerPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string, jwtToken string) (*mgmProto.LoginResponse, error) {
|
func registerPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string, jwtToken string, pubSSHKey []byte) (*mgmProto.LoginResponse, error) {
|
||||||
validSetupKey, err := uuid.Parse(setupKey)
|
validSetupKey, err := uuid.Parse(setupKey)
|
||||||
if err != nil && jwtToken == "" {
|
if err != nil && jwtToken == "" {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "invalid setup-key or no sso information provided, err: %v", err)
|
return nil, status.Errorf(codes.InvalidArgument, "invalid setup-key or no sso information provided, err: %v", err)
|
||||||
@@ -83,7 +86,7 @@ func registerPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.
|
|||||||
|
|
||||||
log.Debugf("sending peer registration request to Management Service")
|
log.Debugf("sending peer registration request to Management Service")
|
||||||
info := system.GetInfo(ctx)
|
info := system.GetInfo(ctx)
|
||||||
loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), jwtToken, info)
|
loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), jwtToken, info, pubSSHKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed registering peer %v,%s", err, validSetupKey.String())
|
log.Errorf("failed registering peer %v,%s", err, validSetupKey.String())
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package peer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl"
|
"golang.zx2c4.com/wireguard/wgctrl"
|
||||||
"net"
|
"net"
|
||||||
@@ -64,6 +65,8 @@ type Conn struct {
|
|||||||
agent *ice.Agent
|
agent *ice.Agent
|
||||||
status ConnStatus
|
status ConnStatus
|
||||||
|
|
||||||
|
statusRecorder *nbStatus.Status
|
||||||
|
|
||||||
proxy proxy.Proxy
|
proxy proxy.Proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,9 +75,14 @@ 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) (*Conn, error) {
|
func NewConn(config ConnConfig, statusRecorder *nbStatus.Status) (*Conn, error) {
|
||||||
return &Conn{
|
return &Conn{
|
||||||
config: config,
|
config: config,
|
||||||
mu: sync.Mutex{},
|
mu: sync.Mutex{},
|
||||||
@@ -82,6 +90,7 @@ func NewConn(config ConnConfig) (*Conn, error) {
|
|||||||
closeCh: make(chan struct{}),
|
closeCh: make(chan struct{}),
|
||||||
remoteOffersCh: make(chan IceCredentials),
|
remoteOffersCh: make(chan IceCredentials),
|
||||||
remoteAnswerCh: make(chan IceCredentials),
|
remoteAnswerCh: make(chan IceCredentials),
|
||||||
|
statusRecorder: statusRecorder,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +166,17 @@ func (conn *Conn) reCreateAgent() error {
|
|||||||
func (conn *Conn) Open() error {
|
func (conn *Conn) Open() error {
|
||||||
log.Debugf("trying to connect to peer %s", conn.config.Key)
|
log.Debugf("trying to connect to peer %s", conn.config.Key)
|
||||||
|
|
||||||
|
peerState := nbStatus.PeerState{PubKey: conn.config.Key}
|
||||||
|
|
||||||
|
peerState.IP = strings.Split(conn.config.ProxyConfig.AllowedIps, "/")[0]
|
||||||
|
peerState.ConnStatusUpdate = time.Now()
|
||||||
|
peerState.ConnStatus = conn.status.String()
|
||||||
|
|
||||||
|
err := conn.statusRecorder.UpdatePeerState(peerState)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("erro while updating the state of peer %s,err: %v", conn.config.Key, err)
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
err := conn.cleanup()
|
err := conn.cleanup()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -165,7 +185,7 @@ func (conn *Conn) Open() error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := conn.reCreateAgent()
|
err = conn.reCreateAgent()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -205,6 +225,15 @@ func (conn *Conn) Open() error {
|
|||||||
defer conn.notifyDisconnected()
|
defer conn.notifyDisconnected()
|
||||||
conn.mu.Unlock()
|
conn.mu.Unlock()
|
||||||
|
|
||||||
|
peerState = nbStatus.PeerState{PubKey: conn.config.Key}
|
||||||
|
|
||||||
|
peerState.ConnStatus = conn.status.String()
|
||||||
|
peerState.ConnStatusUpdate = time.Now()
|
||||||
|
err = conn.statusRecorder.UpdatePeerState(peerState)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("erro while updating the state of peer %s,err: %v", conn.config.Key, err)
|
||||||
|
}
|
||||||
|
|
||||||
err = conn.agent.GatherCandidates()
|
err = conn.agent.GatherCandidates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -224,7 +253,7 @@ func (conn *Conn) Open() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// the 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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -259,6 +288,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
|
||||||
@@ -296,12 +329,15 @@ func (conn *Conn) startProxy(remoteConn net.Conn) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
peerState := nbStatus.PeerState{PubKey: conn.config.Key}
|
||||||
useProxy := shouldUseProxy(pair)
|
useProxy := shouldUseProxy(pair)
|
||||||
var p proxy.Proxy
|
var p proxy.Proxy
|
||||||
if useProxy {
|
if useProxy {
|
||||||
p = proxy.NewWireguardProxy(conn.config.ProxyConfig)
|
p = proxy.NewWireguardProxy(conn.config.ProxyConfig)
|
||||||
|
peerState.Direct = false
|
||||||
} else {
|
} else {
|
||||||
p = proxy.NewNoProxy(conn.config.ProxyConfig)
|
p = proxy.NewNoProxy(conn.config.ProxyConfig)
|
||||||
|
peerState.Direct = true
|
||||||
}
|
}
|
||||||
conn.proxy = p
|
conn.proxy = p
|
||||||
err = p.Start(remoteConn)
|
err = p.Start(remoteConn)
|
||||||
@@ -311,6 +347,19 @@ func (conn *Conn) startProxy(remoteConn net.Conn) error {
|
|||||||
|
|
||||||
conn.status = StatusConnected
|
conn.status = StatusConnected
|
||||||
|
|
||||||
|
peerState.ConnStatus = conn.status.String()
|
||||||
|
peerState.ConnStatusUpdate = time.Now()
|
||||||
|
peerState.LocalIceCandidateType = pair.Local.Type().String()
|
||||||
|
peerState.RemoteIceCandidateType = pair.Remote.Type().String()
|
||||||
|
if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay {
|
||||||
|
peerState.Relayed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conn.statusRecorder.UpdatePeerState(peerState)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("unable to save peer's state, got error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,6 +392,14 @@ func (conn *Conn) cleanup() error {
|
|||||||
|
|
||||||
conn.status = StatusDisconnected
|
conn.status = StatusDisconnected
|
||||||
|
|
||||||
|
peerState := nbStatus.PeerState{PubKey: conn.config.Key}
|
||||||
|
peerState.ConnStatus = conn.status.String()
|
||||||
|
peerState.ConnStatusUpdate = time.Now()
|
||||||
|
err := conn.statusRecorder.UpdatePeerState(peerState)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("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)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -367,7 +424,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 {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package peer
|
|||||||
import (
|
import (
|
||||||
"github.com/magiconair/properties/assert"
|
"github.com/magiconair/properties/assert"
|
||||||
"github.com/netbirdio/netbird/client/internal/proxy"
|
"github.com/netbirdio/netbird/client/internal/proxy"
|
||||||
|
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
"github.com/pion/ice/v2"
|
"github.com/pion/ice/v2"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -32,7 +33,7 @@ func TestNewConn_interfaceFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConn_GetKey(t *testing.T) {
|
func TestConn_GetKey(t *testing.T) {
|
||||||
conn, err := NewConn(connConf)
|
conn, err := NewConn(connConf, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -44,7 +45,7 @@ func TestConn_GetKey(t *testing.T) {
|
|||||||
|
|
||||||
func TestConn_OnRemoteOffer(t *testing.T) {
|
func TestConn_OnRemoteOffer(t *testing.T) {
|
||||||
|
|
||||||
conn, err := NewConn(connConf)
|
conn, err := NewConn(connConf, nbstatus.NewRecorder())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -74,7 +75,7 @@ func TestConn_OnRemoteOffer(t *testing.T) {
|
|||||||
|
|
||||||
func TestConn_OnRemoteAnswer(t *testing.T) {
|
func TestConn_OnRemoteAnswer(t *testing.T) {
|
||||||
|
|
||||||
conn, err := NewConn(connConf)
|
conn, err := NewConn(connConf, nbstatus.NewRecorder())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -103,7 +104,7 @@ func TestConn_OnRemoteAnswer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
func TestConn_Status(t *testing.T) {
|
func TestConn_Status(t *testing.T) {
|
||||||
|
|
||||||
conn, err := NewConn(connConf)
|
conn, err := NewConn(connConf, nbstatus.NewRecorder())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -130,7 +131,7 @@ func TestConn_Status(t *testing.T) {
|
|||||||
|
|
||||||
func TestConn_Close(t *testing.T) {
|
func TestConn_Close(t *testing.T) {
|
||||||
|
|
||||||
conn, err := NewConn(connConf)
|
conn, err := NewConn(connConf, nbstatus.NewRecorder())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ type ConnStatus int
|
|||||||
func (s ConnStatus) String() string {
|
func (s ConnStatus) String() string {
|
||||||
switch s {
|
switch s {
|
||||||
case StatusConnecting:
|
case StatusConnecting:
|
||||||
return "StatusConnecting"
|
return "Connecting"
|
||||||
case StatusConnected:
|
case StatusConnected:
|
||||||
return "StatusConnected"
|
return "Connected"
|
||||||
case StatusDisconnected:
|
case StatusDisconnected:
|
||||||
return "StatusDisconnected"
|
return "Disconnected"
|
||||||
default:
|
default:
|
||||||
log.Errorf("unknown status: %d", s)
|
log.Errorf("unknown status: %d", s)
|
||||||
return "INVALID_PEER_CONNECTION_STATUS"
|
return "INVALID_PEER_CONNECTION_STATUS"
|
||||||
@@ -19,7 +19,7 @@ func (s ConnStatus) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StatusConnected = iota
|
StatusConnected ConnStatus = iota
|
||||||
StatusConnecting
|
StatusConnecting
|
||||||
StatusDisconnected
|
StatusDisconnected
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ func TestConnStatus_String(t *testing.T) {
|
|||||||
status ConnStatus
|
status ConnStatus
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{"StatusConnected", StatusConnected, "StatusConnected"},
|
{"StatusConnected", StatusConnected, "Connected"},
|
||||||
{"StatusDisconnected", StatusDisconnected, "StatusDisconnected"},
|
{"StatusDisconnected", StatusDisconnected, "Disconnected"},
|
||||||
{"StatusConnecting", StatusConnecting, "StatusConnecting"},
|
{"StatusConnecting", StatusConnecting, "Connecting"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, table := range tables {
|
for _, table := range tables {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.19.4
|
// protoc v3.21.2
|
||||||
// source: daemon.proto
|
// source: daemon.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
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"
|
_ "google.golang.org/protobuf/types/descriptorpb"
|
||||||
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
)
|
)
|
||||||
@@ -332,6 +333,8 @@ type StatusRequest struct {
|
|||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
GetFullPeerStatus bool `protobuf:"varint,1,opt,name=getFullPeerStatus,proto3" json:"getFullPeerStatus,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StatusRequest) Reset() {
|
func (x *StatusRequest) Reset() {
|
||||||
@@ -366,13 +369,21 @@ func (*StatusRequest) Descriptor() ([]byte, []int) {
|
|||||||
return file_daemon_proto_rawDescGZIP(), []int{6}
|
return file_daemon_proto_rawDescGZIP(), []int{6}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *StatusRequest) GetGetFullPeerStatus() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.GetFullPeerStatus
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type StatusResponse struct {
|
type StatusResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
// 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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StatusResponse) Reset() {
|
func (x *StatusResponse) Reset() {
|
||||||
@@ -414,6 +425,13 @@ func (x *StatusResponse) GetStatus() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *StatusResponse) GetFullStatus() *FullStatus {
|
||||||
|
if x != nil {
|
||||||
|
return x.FullStatus
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type DownRequest struct {
|
type DownRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -612,83 +630,493 @@ func (x *GetConfigResponse) GetAdminURL() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PeerState contains the latest state of a peer
|
||||||
|
type PeerState struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
IP string `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,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"`
|
||||||
|
ConnStatusUpdate *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=connStatusUpdate,proto3" json:"connStatusUpdate,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"`
|
||||||
|
LocalIceCandidateType string `protobuf:"bytes,7,opt,name=localIceCandidateType,proto3" json:"localIceCandidateType,omitempty"`
|
||||||
|
RemoteIceCandidateType string `protobuf:"bytes,8,opt,name=remoteIceCandidateType,proto3" json:"remoteIceCandidateType,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PeerState) Reset() {
|
||||||
|
*x = PeerState{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[12]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PeerState) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*PeerState) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *PeerState) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[12]
|
||||||
|
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 PeerState.ProtoReflect.Descriptor instead.
|
||||||
|
func (*PeerState) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{12}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PeerState) GetIP() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.IP
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PeerState) GetPubKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.PubKey
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PeerState) GetConnStatus() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ConnStatus
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PeerState) GetConnStatusUpdate() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.ConnStatusUpdate
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PeerState) GetRelayed() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Relayed
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PeerState) GetDirect() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Direct
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PeerState) GetLocalIceCandidateType() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.LocalIceCandidateType
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PeerState) GetRemoteIceCandidateType() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.RemoteIceCandidateType
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalPeerState contains the latest state of the local peer
|
||||||
|
type LocalPeerState struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
IP string `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"`
|
||||||
|
PubKey string `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
|
||||||
|
KernelInterface bool `protobuf:"varint,3,opt,name=kernelInterface,proto3" json:"kernelInterface,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LocalPeerState) Reset() {
|
||||||
|
*x = LocalPeerState{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[13]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LocalPeerState) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LocalPeerState) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LocalPeerState) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[13]
|
||||||
|
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 LocalPeerState.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LocalPeerState) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{13}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LocalPeerState) GetIP() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.IP
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LocalPeerState) GetPubKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.PubKey
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LocalPeerState) GetKernelInterface() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.KernelInterface
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignalState contains the latest state of a signal connection
|
||||||
|
type SignalState struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
URL string `protobuf:"bytes,1,opt,name=URL,proto3" json:"URL,omitempty"`
|
||||||
|
Connected bool `protobuf:"varint,2,opt,name=connected,proto3" json:"connected,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SignalState) Reset() {
|
||||||
|
*x = SignalState{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[14]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SignalState) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SignalState) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SignalState) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[14]
|
||||||
|
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 SignalState.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SignalState) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{14}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SignalState) GetURL() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.URL
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SignalState) GetConnected() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Connected
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ManagementState contains the latest state of a management connection
|
||||||
|
type ManagementState struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
URL string `protobuf:"bytes,1,opt,name=URL,proto3" json:"URL,omitempty"`
|
||||||
|
Connected bool `protobuf:"varint,2,opt,name=connected,proto3" json:"connected,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ManagementState) Reset() {
|
||||||
|
*x = ManagementState{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[15]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ManagementState) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ManagementState) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ManagementState) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[15]
|
||||||
|
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 ManagementState.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ManagementState) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{15}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ManagementState) GetURL() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.URL
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ManagementState) GetConnected() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Connected
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullStatus contains the full state held by the Status instance
|
||||||
|
type FullStatus struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
ManagementState *ManagementState `protobuf:"bytes,1,opt,name=managementState,proto3" json:"managementState,omitempty"`
|
||||||
|
SignalState *SignalState `protobuf:"bytes,2,opt,name=signalState,proto3" json:"signalState,omitempty"`
|
||||||
|
LocalPeerState *LocalPeerState `protobuf:"bytes,3,opt,name=localPeerState,proto3" json:"localPeerState,omitempty"`
|
||||||
|
Peers []*PeerState `protobuf:"bytes,4,rep,name=peers,proto3" json:"peers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FullStatus) Reset() {
|
||||||
|
*x = FullStatus{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[16]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FullStatus) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*FullStatus) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *FullStatus) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[16]
|
||||||
|
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 FullStatus.ProtoReflect.Descriptor instead.
|
||||||
|
func (*FullStatus) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{16}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FullStatus) GetManagementState() *ManagementState {
|
||||||
|
if x != nil {
|
||||||
|
return x.ManagementState
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FullStatus) GetSignalState() *SignalState {
|
||||||
|
if x != nil {
|
||||||
|
return x.SignalState
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FullStatus) GetLocalPeerState() *LocalPeerState {
|
||||||
|
if x != nil {
|
||||||
|
return x.LocalPeerState
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FullStatus) GetPeers() []*PeerState {
|
||||||
|
if x != nil {
|
||||||
|
return x.Peers
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_daemon_proto protoreflect.FileDescriptor
|
var File_daemon_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_daemon_proto_rawDesc = []byte{
|
var file_daemon_proto_rawDesc = []byte{
|
||||||
0x0a, 0x0c, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
|
0x0a, 0x0c, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
|
||||||
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
|
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
|
||||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||||
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x90, 0x01, 0x0a, 0x0c, 0x4c, 0x6f, 0x67,
|
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||||
0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x74,
|
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||||
0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x74,
|
0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x90, 0x01, 0x0a, 0x0c, 0x4c, 0x6f,
|
||||||
0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72,
|
0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65,
|
||||||
0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65,
|
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65,
|
||||||
0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e,
|
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61,
|
||||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72,
|
||||||
0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12,
|
0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61,
|
||||||
0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28,
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||||
0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x22, 0xb5, 0x01, 0x0a, 0x0d,
|
0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a,
|
0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x04, 0x20, 0x01,
|
||||||
0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01,
|
0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x22, 0xb5, 0x01, 0x0a,
|
||||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f,
|
0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24,
|
||||||
0x67, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18,
|
0x0a, 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18,
|
||||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12,
|
0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c,
|
||||||
0x28, 0x0a, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55,
|
0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65,
|
||||||
0x52, 0x49, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69,
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65,
|
||||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72,
|
0x12, 0x28, 0x0a, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
|
0x55, 0x52, 0x49, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66,
|
||||||
|
0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x65,
|
||||||
|
0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d,
|
||||||
|
0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x76, 0x65, 0x72,
|
||||||
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70,
|
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70,
|
||||||
0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69,
|
0x6c, 0x65, 0x74, 0x65, 0x22, 0x31, 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c,
|
||||||
0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c,
|
0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75,
|
||||||
0x65, 0x74, 0x65, 0x22, 0x31, 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f,
|
0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75,
|
||||||
0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73,
|
0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x69, 0x74, 0x53,
|
||||||
0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73,
|
0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||||
0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53,
|
0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a,
|
||||||
0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b,
|
0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x0a, 0x0d, 0x53, 0x74,
|
||||||
0x0a, 0x09, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55,
|
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67,
|
||||||
0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74, 0x61,
|
0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||||
0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x28, 0x0a, 0x0e, 0x53, 0x74,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50,
|
||||||
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06,
|
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x5c, 0x0a, 0x0e, 0x53, 0x74, 0x61,
|
||||||
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74,
|
0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73,
|
||||||
0x61, 0x74, 0x75, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75,
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61,
|
||||||
0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43,
|
0x2e, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c,
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a,
|
0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52,
|
||||||
0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x01,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e,
|
||||||
0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c,
|
0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x47,
|
||||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46,
|
0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x03,
|
0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x22, 0x0a,
|
0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x04, 0x20,
|
0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65,
|
0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66,
|
||||||
0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x05, 0x20,
|
0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x32, 0xf7, 0x02,
|
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65,
|
||||||
0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
|
0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79,
|
||||||
0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65,
|
||||||
0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15,
|
0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
|
||||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73,
|
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53,
|
0x22, 0xbb, 0x02, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e,
|
||||||
0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16,
|
||||||
0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71,
|
0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61,
|
0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74,
|
||||||
0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e,
|
||||||
0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65,
|
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74,
|
||||||
0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e,
|
0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
|
||||||
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||||
0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e,
|
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x6f,
|
||||||
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71,
|
0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x18,
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74,
|
0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33,
|
0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65,
|
||||||
0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
|
||||||
0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61,
|
0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64,
|
||||||
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61,
|
||||||
0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e,
|
0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65,
|
||||||
0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65,
|
0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65,
|
||||||
0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73,
|
0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x62,
|
||||||
0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65,
|
||||||
|
0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50,
|
||||||
|
0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||||
|
0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x72, 0x6e,
|
||||||
|
0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||||
|
0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61,
|
||||||
|
0x63, 0x65, 0x22, 0x3d, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
||||||
|
0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
|
||||||
|
0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64,
|
||||||
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65,
|
||||||
|
0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53,
|
||||||
|
0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
|
0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
|
||||||
|
0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
|
||||||
|
0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01, 0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61,
|
||||||
|
0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
|
0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64,
|
||||||
|
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
|
0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
|
0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c,
|
||||||
|
0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61,
|
||||||
|
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65,
|
||||||
|
0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a,
|
||||||
|
0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18,
|
||||||
|
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c,
|
||||||
|
0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c,
|
||||||
|
0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a,
|
||||||
|
0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64,
|
||||||
|
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52,
|
||||||
|
0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32, 0xf7, 0x02, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f,
|
||||||
|
0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69,
|
||||||
|
0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
||||||
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
||||||
|
0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||||
|
0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
||||||
|
0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53,
|
||||||
|
0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e,
|
||||||
|
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f,
|
||||||
|
0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a,
|
||||||
|
0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52,
|
||||||
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
|
0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06,
|
||||||
|
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
|
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e,
|
||||||
|
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12,
|
||||||
|
0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f,
|
||||||
|
0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09,
|
||||||
|
0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
||||||
|
0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 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 (
|
||||||
@@ -703,39 +1131,51 @@ func file_daemon_proto_rawDescGZIP() []byte {
|
|||||||
return file_daemon_proto_rawDescData
|
return file_daemon_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
|
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
|
||||||
|
(*LocalPeerState)(nil), // 13: daemon.LocalPeerState
|
||||||
|
(*SignalState)(nil), // 14: daemon.SignalState
|
||||||
|
(*ManagementState)(nil), // 15: daemon.ManagementState
|
||||||
|
(*FullStatus)(nil), // 16: daemon.FullStatus
|
||||||
|
(*timestamppb.Timestamp)(nil), // 17: google.protobuf.Timestamp
|
||||||
}
|
}
|
||||||
var file_daemon_proto_depIdxs = []int32{
|
var file_daemon_proto_depIdxs = []int32{
|
||||||
0, // 0: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
|
16, // 0: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus
|
||||||
2, // 1: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
|
17, // 1: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp
|
||||||
4, // 2: daemon.DaemonService.Up:input_type -> daemon.UpRequest
|
15, // 2: daemon.FullStatus.managementState:type_name -> daemon.ManagementState
|
||||||
6, // 3: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
|
14, // 3: daemon.FullStatus.signalState:type_name -> daemon.SignalState
|
||||||
8, // 4: daemon.DaemonService.Down:input_type -> daemon.DownRequest
|
13, // 4: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState
|
||||||
10, // 5: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
|
12, // 5: daemon.FullStatus.peers:type_name -> daemon.PeerState
|
||||||
1, // 6: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
0, // 6: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
|
||||||
3, // 7: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
|
2, // 7: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
|
||||||
5, // 8: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
4, // 8: daemon.DaemonService.Up:input_type -> daemon.UpRequest
|
||||||
7, // 9: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
6, // 9: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
|
||||||
9, // 10: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
8, // 10: daemon.DaemonService.Down:input_type -> daemon.DownRequest
|
||||||
11, // 11: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
|
10, // 11: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
|
||||||
6, // [6:12] is the sub-list for method output_type
|
1, // 12: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
||||||
0, // [0:6] is the sub-list for method input_type
|
3, // 13: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
|
||||||
0, // [0:0] is the sub-list for extension type_name
|
5, // 14: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
||||||
0, // [0:0] is the sub-list for extension extendee
|
7, // 15: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
||||||
0, // [0:0] is the sub-list for field type_name
|
9, // 16: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
||||||
|
11, // 17: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
|
||||||
|
12, // [12:18] is the sub-list for method output_type
|
||||||
|
6, // [6:12] is the sub-list for method input_type
|
||||||
|
6, // [6:6] is the sub-list for extension type_name
|
||||||
|
6, // [6:6] is the sub-list for extension extendee
|
||||||
|
0, // [0:6] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_daemon_proto_init() }
|
func init() { file_daemon_proto_init() }
|
||||||
@@ -888,6 +1328,66 @@ func file_daemon_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
file_daemon_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*PeerState); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_daemon_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*LocalPeerState); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_daemon_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*SignalState); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_daemon_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ManagementState); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_daemon_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*FullStatus); 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{
|
||||||
@@ -895,7 +1395,7 @@ func file_daemon_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_daemon_proto_rawDesc,
|
RawDescriptor: file_daemon_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 12,
|
NumMessages: 17,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
import "google/protobuf/descriptor.proto";
|
import "google/protobuf/descriptor.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
option go_package = "/proto";
|
option go_package = "/proto";
|
||||||
|
|
||||||
@@ -59,11 +60,14 @@ message UpRequest {}
|
|||||||
|
|
||||||
message UpResponse {}
|
message UpResponse {}
|
||||||
|
|
||||||
message StatusRequest{}
|
message StatusRequest{
|
||||||
|
bool getFullPeerStatus = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message StatusResponse{
|
message StatusResponse{
|
||||||
// status of the server.
|
// status of the server.
|
||||||
string status = 1;
|
string status = 1;
|
||||||
|
FullStatus fullStatus = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DownRequest {}
|
message DownRequest {}
|
||||||
@@ -88,3 +92,41 @@ message GetConfigResponse {
|
|||||||
// adminURL settings value.
|
// adminURL settings value.
|
||||||
string adminURL = 5;
|
string adminURL = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PeerState contains the latest state of a peer
|
||||||
|
message PeerState {
|
||||||
|
string IP = 1;
|
||||||
|
string pubKey = 2;
|
||||||
|
string connStatus = 3;
|
||||||
|
google.protobuf.Timestamp connStatusUpdate = 4;
|
||||||
|
bool relayed = 5;
|
||||||
|
bool direct = 6;
|
||||||
|
string localIceCandidateType = 7;
|
||||||
|
string remoteIceCandidateType =8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalPeerState contains the latest state of the local peer
|
||||||
|
message LocalPeerState {
|
||||||
|
string IP = 1;
|
||||||
|
string pubKey = 2;
|
||||||
|
bool kernelInterface =3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignalState contains the latest state of a signal connection
|
||||||
|
message SignalState {
|
||||||
|
string URL = 1;
|
||||||
|
bool connected = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ManagementState contains the latest state of a management connection
|
||||||
|
message ManagementState {
|
||||||
|
string URL = 1;
|
||||||
|
bool connected = 2;
|
||||||
|
}
|
||||||
|
// FullStatus contains the full state held by the Status instance
|
||||||
|
message FullStatus {
|
||||||
|
ManagementState managementState = 1;
|
||||||
|
SignalState signalState = 2;
|
||||||
|
LocalPeerState localPeerState = 3;
|
||||||
|
repeated PeerState peers = 4;
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -31,6 +33,8 @@ type Server struct {
|
|||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
config *internal.Config
|
config *internal.Config
|
||||||
proto.UnimplementedDaemonServiceServer
|
proto.UnimplementedDaemonServiceServer
|
||||||
|
|
||||||
|
statusRecorder *nbStatus.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
type oauthAuthFlow struct {
|
type oauthAuthFlow struct {
|
||||||
@@ -52,6 +56,8 @@ func New(ctx context.Context, managementURL, adminURL, configPath, logFile strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
func (s *Server) Start() error {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
state := internal.CtxGetState(s.rootCtx)
|
||||||
|
|
||||||
// if current state contains any error, return it
|
// if current state contains any error, return it
|
||||||
@@ -86,11 +92,16 @@ 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
|
||||||
|
|
||||||
|
if s.statusRecorder == nil {
|
||||||
|
s.statusRecorder = nbStatus.NewRecorder()
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := internal.RunClient(ctx, config); err != nil {
|
if err := internal.RunClient(ctx, config, s.statusRecorder); err != nil {
|
||||||
log.Errorf("init connections: %v", err)
|
log.Errorf("init connections: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -158,6 +169,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()
|
||||||
@@ -350,8 +367,12 @@ func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpR
|
|||||||
return nil, fmt.Errorf("config is not defined, please call login command first")
|
return nil, fmt.Errorf("config is not defined, please call login command first")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.statusRecorder == nil {
|
||||||
|
s.statusRecorder = nbStatus.NewRecorder()
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := internal.RunClient(ctx, s.config); err != nil {
|
if err := internal.RunClient(ctx, s.config, s.statusRecorder); err != nil {
|
||||||
log.Errorf("run client connection: %v", state.Wrap(err))
|
log.Errorf("run client connection: %v", state.Wrap(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -375,7 +396,7 @@ func (s *Server) Down(ctx context.Context, msg *proto.DownRequest) (*proto.DownR
|
|||||||
|
|
||||||
// Status starts engine work in the daemon.
|
// Status starts engine work in the daemon.
|
||||||
func (s *Server) Status(
|
func (s *Server) Status(
|
||||||
ctx context.Context,
|
_ context.Context,
|
||||||
msg *proto.StatusRequest,
|
msg *proto.StatusRequest,
|
||||||
) (*proto.StatusResponse, error) {
|
) (*proto.StatusResponse, error) {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
@@ -386,7 +407,19 @@ func (s *Server) Status(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &proto.StatusResponse{Status: string(status)}, nil
|
statusResponse := proto.StatusResponse{Status: string(status)}
|
||||||
|
|
||||||
|
if s.statusRecorder == nil {
|
||||||
|
s.statusRecorder = nbStatus.NewRecorder()
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.GetFullPeerStatus {
|
||||||
|
fullStatus := s.statusRecorder.GetFullStatus()
|
||||||
|
pbFullStatus := toProtoFullStatus(fullStatus)
|
||||||
|
statusResponse.FullStatus = pbFullStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
return &statusResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfig of the daemon.
|
// GetConfig of the daemon.
|
||||||
@@ -422,3 +455,37 @@ func (s *Server) GetConfig(ctx context.Context, msg *proto.GetConfigRequest) (*p
|
|||||||
PreSharedKey: preSharedKey,
|
PreSharedKey: preSharedKey,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toProtoFullStatus(fullStatus nbStatus.FullStatus) *proto.FullStatus {
|
||||||
|
pbFullStatus := proto.FullStatus{
|
||||||
|
ManagementState: &proto.ManagementState{},
|
||||||
|
SignalState: &proto.SignalState{},
|
||||||
|
LocalPeerState: &proto.LocalPeerState{},
|
||||||
|
Peers: []*proto.PeerState{},
|
||||||
|
}
|
||||||
|
|
||||||
|
pbFullStatus.ManagementState.URL = fullStatus.ManagementState.URL
|
||||||
|
pbFullStatus.ManagementState.Connected = fullStatus.ManagementState.Connected
|
||||||
|
|
||||||
|
pbFullStatus.SignalState.URL = fullStatus.SignalState.URL
|
||||||
|
pbFullStatus.SignalState.Connected = fullStatus.SignalState.Connected
|
||||||
|
|
||||||
|
pbFullStatus.LocalPeerState.IP = fullStatus.LocalPeerState.IP
|
||||||
|
pbFullStatus.LocalPeerState.PubKey = fullStatus.LocalPeerState.PubKey
|
||||||
|
pbFullStatus.LocalPeerState.KernelInterface = fullStatus.LocalPeerState.KernelInterface
|
||||||
|
|
||||||
|
for _, peerState := range fullStatus.Peers {
|
||||||
|
pbPeerState := &proto.PeerState{
|
||||||
|
IP: peerState.IP,
|
||||||
|
PubKey: peerState.PubKey,
|
||||||
|
ConnStatus: peerState.ConnStatus,
|
||||||
|
ConnStatusUpdate: timestamppb.New(peerState.ConnStatusUpdate),
|
||||||
|
Relayed: peerState.Relayed,
|
||||||
|
Direct: peerState.Direct,
|
||||||
|
LocalIceCandidateType: peerState.LocalIceCandidateType,
|
||||||
|
RemoteIceCandidateType: peerState.RemoteIceCandidateType,
|
||||||
|
}
|
||||||
|
pbFullStatus.Peers = append(pbFullStatus.Peers, pbPeerState)
|
||||||
|
}
|
||||||
|
return &pbFullStatus
|
||||||
|
}
|
||||||
|
|||||||
116
client/ssh/client.go
Normal file
116
client/ssh/client.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/term"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client wraps crypto/ssh Client to simplify usage
|
||||||
|
type Client struct {
|
||||||
|
client *ssh.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the wrapped SSH Client
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
return c.client.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenTerminal starts an interactive terminal session with the remote SSH server
|
||||||
|
func (c *Client) OpenTerminal() error {
|
||||||
|
session, err := c.client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open new session: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := session.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
fd := int(os.Stdout.Fd())
|
||||||
|
state, err := term.MakeRaw(fd)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run raw terminal: %s", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := term.Restore(fd, state)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
w, h, err := term.GetSize(fd)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("terminal get size: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
modes := ssh.TerminalModes{
|
||||||
|
ssh.ECHO: 1,
|
||||||
|
ssh.TTY_OP_ISPEED: 14400,
|
||||||
|
ssh.TTY_OP_OSPEED: 14400,
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal := os.Getenv("TERM")
|
||||||
|
if terminal == "" {
|
||||||
|
terminal = "xterm-256color"
|
||||||
|
}
|
||||||
|
if err := session.RequestPty(terminal, h, w, modes); err != nil {
|
||||||
|
return fmt.Errorf("failed requesting pty session with xterm: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Stdout = os.Stdout
|
||||||
|
session.Stderr = os.Stderr
|
||||||
|
session.Stdin = os.Stdin
|
||||||
|
|
||||||
|
if err := session.Shell(); err != nil {
|
||||||
|
return fmt.Errorf("failed to start login shell on the remote host: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := session.Wait(); err != nil {
|
||||||
|
if e, ok := err.(*ssh.ExitError); ok {
|
||||||
|
switch e.ExitStatus() {
|
||||||
|
case 130:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed running SSH session: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialWithKey connects to the remote SSH server with a provided private key file (PEM).
|
||||||
|
func DialWithKey(addr, user string, privateKey []byte) (*Client, error) {
|
||||||
|
|
||||||
|
signer, err := ssh.ParsePrivateKey(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: user,
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
Auth: []ssh.AuthMethod{
|
||||||
|
ssh.PublicKeys(signer),
|
||||||
|
},
|
||||||
|
HostKeyCallback: ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }),
|
||||||
|
}
|
||||||
|
|
||||||
|
return Dial("tcp", addr, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the remote SSH server.
|
||||||
|
func Dial(network, addr string, config *ssh.ClientConfig) (*Client, error) {
|
||||||
|
client, err := ssh.Dial(network, addr, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Client{
|
||||||
|
client: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
36
client/ssh/login.go
Normal file
36
client/ssh/login.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/util"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getLoginCmd(user string, remoteAddr net.Addr) (loginPath string, args []string, err error) {
|
||||||
|
loginPath, err = exec.LookPath("login")
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addrPort, err := netip.ParseAddrPort(remoteAddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
|
||||||
|
if util.FileExists("/etc/arch-release") && !util.FileExists("/etc/pam.d/remote") {
|
||||||
|
// detect if Arch Linux
|
||||||
|
return loginPath, []string{"-f", user, "-p"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return loginPath, []string{"-f", user, "-h", addrPort.Addr().String(), "-p"}, nil
|
||||||
|
} else if runtime.GOOS == "darwin" {
|
||||||
|
return loginPath, []string{"-fp", "-h", addrPort.Addr().String(), user}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil, fmt.Errorf("unsupported platform")
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
250
client/ssh/server.go
Normal file
250
client/ssh/server.go
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/creack/pty"
|
||||||
|
"github.com/gliderlabs/ssh"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultSSHPort is the default SSH port of the NetBird's embedded SSH server
|
||||||
|
const DefaultSSHPort = 44338
|
||||||
|
|
||||||
|
// DefaultSSHServer is a function that creates DefaultServer
|
||||||
|
func DefaultSSHServer(hostKeyPEM []byte, addr string) (Server, error) {
|
||||||
|
return newDefaultServer(hostKeyPEM, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server is an interface of SSH server
|
||||||
|
type Server interface {
|
||||||
|
// Stop stops SSH server.
|
||||||
|
Stop() error
|
||||||
|
// Start starts SSH server. Blocking
|
||||||
|
Start() error
|
||||||
|
// RemoveAuthorizedKey removes SSH key of a given peer from the authorized keys
|
||||||
|
RemoveAuthorizedKey(peer string)
|
||||||
|
// AddAuthorizedKey add a given peer key to server authorized keys
|
||||||
|
AddAuthorizedKey(peer, newKey string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultServer is the embedded NetBird SSH server
|
||||||
|
type DefaultServer struct {
|
||||||
|
listener net.Listener
|
||||||
|
// authorizedKeys is ssh pub key indexed by peer WireGuard public key
|
||||||
|
authorizedKeys map[string]ssh.PublicKey
|
||||||
|
mu sync.Mutex
|
||||||
|
hostKeyPEM []byte
|
||||||
|
sessions []ssh.Session
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDefaultServer creates new server with provided host key
|
||||||
|
func newDefaultServer(hostKeyPEM []byte, addr string) (*DefaultServer, error) {
|
||||||
|
ln, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
allowedKeys := make(map[string]ssh.PublicKey)
|
||||||
|
return &DefaultServer{listener: ln, mu: sync.Mutex{}, hostKeyPEM: hostKeyPEM, authorizedKeys: allowedKeys, sessions: make([]ssh.Session, 0)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAuthorizedKey removes SSH key of a given peer from the authorized keys
|
||||||
|
func (srv *DefaultServer) RemoveAuthorizedKey(peer string) {
|
||||||
|
srv.mu.Lock()
|
||||||
|
defer srv.mu.Unlock()
|
||||||
|
|
||||||
|
delete(srv.authorizedKeys, peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAuthorizedKey add a given peer key to server authorized keys
|
||||||
|
func (srv *DefaultServer) AddAuthorizedKey(peer, newKey string) error {
|
||||||
|
srv.mu.Lock()
|
||||||
|
defer srv.mu.Unlock()
|
||||||
|
|
||||||
|
parsedKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(newKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.authorizedKeys[peer] = parsedKey
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops SSH server.
|
||||||
|
func (srv *DefaultServer) Stop() error {
|
||||||
|
srv.mu.Lock()
|
||||||
|
defer srv.mu.Unlock()
|
||||||
|
err := srv.listener.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, session := range srv.sessions {
|
||||||
|
err := session.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed closing SSH session from %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *DefaultServer) publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
|
||||||
|
srv.mu.Lock()
|
||||||
|
defer srv.mu.Unlock()
|
||||||
|
|
||||||
|
for _, allowed := range srv.authorizedKeys {
|
||||||
|
if ssh.KeysEqual(allowed, key) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareUserEnv(user *user.User, shell string) []string {
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf("SHELL=" + shell),
|
||||||
|
fmt.Sprintf("USER=" + user.Username),
|
||||||
|
fmt.Sprintf("HOME=" + user.HomeDir),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func acceptEnv(s string) bool {
|
||||||
|
split := strings.Split(s, "=")
|
||||||
|
if len(split) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return split[0] == "TERM" || split[0] == "LANG" || strings.HasPrefix(split[0], "LC_")
|
||||||
|
}
|
||||||
|
|
||||||
|
// sessionHandler handles SSH session post auth
|
||||||
|
func (srv *DefaultServer) sessionHandler(session ssh.Session) {
|
||||||
|
srv.mu.Lock()
|
||||||
|
srv.sessions = append(srv.sessions, session)
|
||||||
|
srv.mu.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := session.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
localUser, err := userNameLookup(session.User())
|
||||||
|
if err != nil {
|
||||||
|
_, err = fmt.Fprintf(session, "remote SSH server couldn't find local user %s\n", session.User()) //nolint
|
||||||
|
err = session.Exit(1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Warnf("failed SSH session from %v, user %s", session.RemoteAddr(), session.User())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ptyReq, winCh, isPty := session.Pty()
|
||||||
|
if isPty {
|
||||||
|
loginCmd, loginArgs, err := getLoginCmd(localUser.Username, session.RemoteAddr())
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed logging-in user %s from remote IP %s", localUser.Username, session.RemoteAddr().String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd := exec.Command(loginCmd, loginArgs...)
|
||||||
|
go func() {
|
||||||
|
<-session.Context().Done()
|
||||||
|
err := cmd.Process.Kill()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
cmd.Dir = localUser.HomeDir
|
||||||
|
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term))
|
||||||
|
cmd.Env = append(cmd.Env, prepareUserEnv(localUser, getUserShell(localUser.Uid))...)
|
||||||
|
for _, v := range session.Environ() {
|
||||||
|
if acceptEnv(v) {
|
||||||
|
cmd.Env = append(cmd.Env, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := pty.Start(cmd)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed starting SSH server %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for win := range winCh {
|
||||||
|
setWinSize(file, win.Width, win.Height)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
srv.stdInOut(file, session)
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err := io.WriteString(session, "only PTY is supported.\n")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = session.Exit(1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *DefaultServer) stdInOut(file *os.File, session ssh.Session) {
|
||||||
|
go func() {
|
||||||
|
// stdin
|
||||||
|
_, err := io.Copy(file, session)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// stdout
|
||||||
|
_, err := io.Copy(session, file)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts SSH server. Blocking
|
||||||
|
func (srv *DefaultServer) Start() error {
|
||||||
|
log.Infof("starting SSH server on addr: %s", srv.listener.Addr().String())
|
||||||
|
|
||||||
|
publicKeyOption := ssh.PublicKeyAuth(srv.publicKeyHandler)
|
||||||
|
hostKeyPEM := ssh.HostKeyPEM(srv.hostKeyPEM)
|
||||||
|
err := ssh.Serve(srv.listener, srv.sessionHandler, publicKeyOption, hostKeyPEM)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserShell(userID string) string {
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
output, _ := exec.Command("getent", "passwd", userID).Output()
|
||||||
|
line := strings.SplitN(string(output), ":", 10)
|
||||||
|
if len(line) > 6 {
|
||||||
|
return strings.TrimSpace(line[6])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shell := os.Getenv("SHELL")
|
||||||
|
if shell == "" {
|
||||||
|
shell = "/bin/sh"
|
||||||
|
}
|
||||||
|
return shell
|
||||||
|
}
|
||||||
44
client/ssh/server_mock.go
Normal file
44
client/ssh/server_mock.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// MockServer mocks ssh.Server
|
||||||
|
type MockServer struct {
|
||||||
|
Ctx context.Context
|
||||||
|
StopFunc func() error
|
||||||
|
StartFunc func() error
|
||||||
|
AddAuthorizedKeyFunc func(peer, newKey string) error
|
||||||
|
RemoveAuthorizedKeyFunc func(peer string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAuthorizedKey removes SSH key of a given peer from the authorized keys
|
||||||
|
func (srv *MockServer) RemoveAuthorizedKey(peer string) {
|
||||||
|
if srv.RemoveAuthorizedKeyFunc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srv.RemoveAuthorizedKeyFunc(peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAuthorizedKey add a given peer key to server authorized keys
|
||||||
|
func (srv *MockServer) AddAuthorizedKey(peer, newKey string) error {
|
||||||
|
if srv.AddAuthorizedKeyFunc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return srv.AddAuthorizedKeyFunc(peer, newKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops SSH server.
|
||||||
|
func (srv *MockServer) Stop() error {
|
||||||
|
if srv.StopFunc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return srv.StopFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts SSH server. Blocking
|
||||||
|
func (srv *MockServer) Start() error {
|
||||||
|
if srv.StartFunc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return srv.StartFunc()
|
||||||
|
}
|
||||||
121
client/ssh/server_test.go
Normal file
121
client/ssh/server_test.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServer_AddAuthorizedKey(t *testing.T) {
|
||||||
|
key, err := GeneratePrivateKey(ED25519)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
server, err := newDefaultServer(key, "localhost:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add multiple keys
|
||||||
|
keys := map[string][]byte{}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
peer := fmt.Sprintf("%s-%d", "remotePeer", i)
|
||||||
|
remotePrivKey, err := GeneratePrivateKey(ED25519)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
remotePubKey, err := GeneratePublicKey(remotePrivKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = server.AddAuthorizedKey(peer, string(remotePubKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
keys[peer] = remotePubKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure that all keys have been added
|
||||||
|
for peer, remotePubKey := range keys {
|
||||||
|
k, ok := server.authorizedKeys[peer]
|
||||||
|
assert.True(t, ok, "expecting remotePeer key to be found in authorizedKeys")
|
||||||
|
|
||||||
|
assert.Equal(t, string(remotePubKey), strings.TrimSpace(string(ssh.MarshalAuthorizedKey(k))))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_RemoveAuthorizedKey(t *testing.T) {
|
||||||
|
key, err := GeneratePrivateKey(ED25519)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
server, err := newDefaultServer(key, "localhost:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
remotePrivKey, err := GeneratePrivateKey(ED25519)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
remotePubKey, err := GeneratePublicKey(remotePrivKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = server.AddAuthorizedKey("remotePeer", string(remotePubKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server.RemoveAuthorizedKey("remotePeer")
|
||||||
|
|
||||||
|
_, ok := server.authorizedKeys["remotePeer"]
|
||||||
|
assert.False(t, ok, "expecting remotePeer's SSH key to be removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_PubKeyHandler(t *testing.T) {
|
||||||
|
key, err := GeneratePrivateKey(ED25519)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
server, err := newDefaultServer(key, "localhost:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys []ssh.PublicKey
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
peer := fmt.Sprintf("%s-%d", "remotePeer", i)
|
||||||
|
remotePrivKey, err := GeneratePrivateKey(ED25519)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
remotePubKey, err := GeneratePublicKey(remotePrivKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteParsedPubKey, _, _, _, err := ssh.ParseAuthorizedKey(remotePubKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = server.AddAuthorizedKey(peer, string(remotePubKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
keys = append(keys, remoteParsedPubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
accepted := server.publicKeyHandler(nil, key)
|
||||||
|
|
||||||
|
assert.Truef(t, accepted, "expecting SSH connection to be accepted for a given SSH key %s", string(ssh.MarshalAuthorizedKey(key)))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
86
client/ssh/util.go
Normal file
86
client/ssh/util.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
gossh "golang.org/x/crypto/ssh"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyType is a type of SSH key
|
||||||
|
type KeyType string
|
||||||
|
|
||||||
|
// ED25519 is key of type ed25519
|
||||||
|
const ED25519 KeyType = "ed25519"
|
||||||
|
|
||||||
|
// ECDSA is key of type ecdsa
|
||||||
|
const ECDSA KeyType = "ecdsa"
|
||||||
|
|
||||||
|
// RSA is key of type rsa
|
||||||
|
const RSA KeyType = "rsa"
|
||||||
|
|
||||||
|
// RSAKeySize is a size of newly generated RSA key
|
||||||
|
const RSAKeySize = 2048
|
||||||
|
|
||||||
|
// GeneratePrivateKey creates RSA Private Key of specified byte size
|
||||||
|
func GeneratePrivateKey(keyType KeyType) ([]byte, error) {
|
||||||
|
|
||||||
|
var key crypto.Signer
|
||||||
|
var err error
|
||||||
|
switch keyType {
|
||||||
|
case ED25519:
|
||||||
|
_, key, err = ed25519.GenerateKey(rand.Reader)
|
||||||
|
case ECDSA:
|
||||||
|
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
case RSA:
|
||||||
|
key, err = rsa.GenerateKey(rand.Reader, RSAKeySize)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported ket type %s", keyType)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pemBytes, err := EncodePrivateKeyToPEM(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pemBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneratePublicKey returns the public part of the private key
|
||||||
|
func GeneratePublicKey(key []byte) ([]byte, error) {
|
||||||
|
signer, err := gossh.ParsePrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
strKey := strings.TrimSpace(string(gossh.MarshalAuthorizedKey(signer.PublicKey())))
|
||||||
|
return []byte(strKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodePrivateKeyToPEM encodes Private Key from RSA to PEM format
|
||||||
|
func EncodePrivateKeyToPEM(privateKey crypto.Signer) ([]byte, error) {
|
||||||
|
mk, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// pem.Block
|
||||||
|
privBlock := pem.Block{
|
||||||
|
Type: "PRIVATE KEY",
|
||||||
|
Bytes: mk,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private key in PEM format
|
||||||
|
privatePEM := pem.EncodeToMemory(&privBlock)
|
||||||
|
return privatePEM, nil
|
||||||
|
}
|
||||||
14
client/ssh/window_unix.go
Normal file
14
client/ssh/window_unix.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setWinSize(file *os.File, width, height int) {
|
||||||
|
syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), uintptr(syscall.TIOCSWINSZ), //nolint
|
||||||
|
uintptr(unsafe.Pointer(&struct{ h, w, x, y uint16 }{uint16(height), uint16(width), 0, 0})))
|
||||||
|
}
|
||||||
9
client/ssh/window_windows.go
Normal file
9
client/ssh/window_windows.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setWinSize(file *os.File, width, height int) {
|
||||||
|
|
||||||
|
}
|
||||||
191
client/status/status.go
Normal file
191
client/status/status.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package status
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PeerState contains the latest state of a peer
|
||||||
|
type PeerState struct {
|
||||||
|
IP string
|
||||||
|
PubKey string
|
||||||
|
ConnStatus string
|
||||||
|
ConnStatusUpdate time.Time
|
||||||
|
Relayed bool
|
||||||
|
Direct bool
|
||||||
|
LocalIceCandidateType string
|
||||||
|
RemoteIceCandidateType string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalPeerState contains the latest state of the local peer
|
||||||
|
type LocalPeerState struct {
|
||||||
|
IP string
|
||||||
|
PubKey string
|
||||||
|
KernelInterface bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignalState contains the latest state of a signal connection
|
||||||
|
type SignalState struct {
|
||||||
|
URL string
|
||||||
|
Connected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ManagementState contains the latest state of a management connection
|
||||||
|
type ManagementState struct {
|
||||||
|
URL string
|
||||||
|
Connected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullStatus contains the full state held by the Status instance
|
||||||
|
type FullStatus struct {
|
||||||
|
Peers []PeerState
|
||||||
|
ManagementState ManagementState
|
||||||
|
SignalState SignalState
|
||||||
|
LocalPeerState LocalPeerState
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status holds a state of peers, signal and management connections
|
||||||
|
type Status struct {
|
||||||
|
mux sync.Mutex
|
||||||
|
peers map[string]PeerState
|
||||||
|
signal SignalState
|
||||||
|
management ManagementState
|
||||||
|
localPeer LocalPeerState
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRecorder returns a new Status instance
|
||||||
|
func NewRecorder() *Status {
|
||||||
|
return &Status{
|
||||||
|
peers: make(map[string]PeerState),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPeer adds peer to Daemon status map
|
||||||
|
func (d *Status) AddPeer(peerPubKey string) error {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
|
_, ok := d.peers[peerPubKey]
|
||||||
|
if ok {
|
||||||
|
return errors.New("peer already exist")
|
||||||
|
}
|
||||||
|
d.peers[peerPubKey] = PeerState{PubKey: peerPubKey}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemovePeer removes peer from Daemon status map
|
||||||
|
func (d *Status) RemovePeer(peerPubKey string) error {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
|
_, ok := d.peers[peerPubKey]
|
||||||
|
if ok {
|
||||||
|
delete(d.peers, peerPubKey)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("no peer with to remove")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePeerState updates peer status
|
||||||
|
func (d *Status) UpdatePeerState(receivedState PeerState) error {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
|
peerState, ok := d.peers[receivedState.PubKey]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("peer doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
if receivedState.IP != "" {
|
||||||
|
peerState.IP = receivedState.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
if receivedState.ConnStatus != peerState.ConnStatus {
|
||||||
|
peerState.ConnStatus = receivedState.ConnStatus
|
||||||
|
peerState.ConnStatusUpdate = receivedState.ConnStatusUpdate
|
||||||
|
peerState.Direct = receivedState.Direct
|
||||||
|
peerState.Relayed = receivedState.Relayed
|
||||||
|
peerState.LocalIceCandidateType = receivedState.LocalIceCandidateType
|
||||||
|
peerState.RemoteIceCandidateType = receivedState.RemoteIceCandidateType
|
||||||
|
}
|
||||||
|
|
||||||
|
d.peers[receivedState.PubKey] = peerState
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLocalPeerState updates local peer status
|
||||||
|
func (d *Status) UpdateLocalPeerState(localPeerState LocalPeerState) {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
|
d.localPeer = localPeerState
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanLocalPeerState cleans local peer status
|
||||||
|
func (d *Status) CleanLocalPeerState() {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
|
d.localPeer = LocalPeerState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkManagementDisconnected sets ManagementState to disconnected
|
||||||
|
func (d *Status) MarkManagementDisconnected(managementURL string) {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
d.management = ManagementState{
|
||||||
|
URL: managementURL,
|
||||||
|
Connected: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkManagementConnected sets ManagementState to connected
|
||||||
|
func (d *Status) MarkManagementConnected(managementURL string) {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
d.management = ManagementState{
|
||||||
|
URL: managementURL,
|
||||||
|
Connected: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkSignalDisconnected sets SignalState to disconnected
|
||||||
|
func (d *Status) MarkSignalDisconnected(signalURL string) {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
d.signal = SignalState{
|
||||||
|
signalURL,
|
||||||
|
false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkSignalConnected sets SignalState to connected
|
||||||
|
func (d *Status) MarkSignalConnected(signalURL string) {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
d.signal = SignalState{
|
||||||
|
signalURL,
|
||||||
|
true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFullStatus gets full status
|
||||||
|
func (d *Status) GetFullStatus() FullStatus {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
|
fullStatus := FullStatus{
|
||||||
|
ManagementState: d.management,
|
||||||
|
SignalState: d.signal,
|
||||||
|
LocalPeerState: d.localPeer,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, status := range d.peers {
|
||||||
|
fullStatus.Peers = append(fullStatus.Peers, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullStatus
|
||||||
|
}
|
||||||
185
client/status/status_test.go
Normal file
185
client/status/status_test.go
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
package status
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddPeer(t *testing.T) {
|
||||||
|
key := "abc"
|
||||||
|
status := NewRecorder()
|
||||||
|
err := status.AddPeer(key)
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
_, exists := status.peers[key]
|
||||||
|
assert.True(t, exists, "value was found")
|
||||||
|
|
||||||
|
err = status.AddPeer(key)
|
||||||
|
|
||||||
|
assert.Error(t, err, "should return error on duplicate")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdatePeerState(t *testing.T) {
|
||||||
|
key := "abc"
|
||||||
|
ip := "10.10.10.10"
|
||||||
|
status := NewRecorder()
|
||||||
|
peerState := PeerState{
|
||||||
|
PubKey: key,
|
||||||
|
}
|
||||||
|
|
||||||
|
status.peers[key] = peerState
|
||||||
|
|
||||||
|
peerState.IP = ip
|
||||||
|
|
||||||
|
err := status.UpdatePeerState(peerState)
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
state, exists := status.peers[key]
|
||||||
|
assert.True(t, exists, "state should be found")
|
||||||
|
assert.Equal(t, ip, state.IP, "ip should be equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemovePeer(t *testing.T) {
|
||||||
|
key := "abc"
|
||||||
|
status := NewRecorder()
|
||||||
|
peerState := PeerState{
|
||||||
|
PubKey: key,
|
||||||
|
}
|
||||||
|
|
||||||
|
status.peers[key] = peerState
|
||||||
|
|
||||||
|
err := status.RemovePeer(key)
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
_, exists := status.peers[key]
|
||||||
|
assert.False(t, exists, "state value shouldn't be found")
|
||||||
|
|
||||||
|
err = status.RemovePeer("not existing")
|
||||||
|
assert.Error(t, err, "should return error when peer doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateLocalPeerState(t *testing.T) {
|
||||||
|
localPeerState := LocalPeerState{
|
||||||
|
IP: "10.10.10.10",
|
||||||
|
PubKey: "abc",
|
||||||
|
KernelInterface: false,
|
||||||
|
}
|
||||||
|
status := NewRecorder()
|
||||||
|
|
||||||
|
status.UpdateLocalPeerState(localPeerState)
|
||||||
|
|
||||||
|
assert.Equal(t, localPeerState, status.localPeer, "local peer status should be equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanLocalPeerState(t *testing.T) {
|
||||||
|
emptyLocalPeerState := LocalPeerState{}
|
||||||
|
localPeerState := LocalPeerState{
|
||||||
|
IP: "10.10.10.10",
|
||||||
|
PubKey: "abc",
|
||||||
|
KernelInterface: false,
|
||||||
|
}
|
||||||
|
status := NewRecorder()
|
||||||
|
|
||||||
|
status.localPeer = localPeerState
|
||||||
|
|
||||||
|
status.CleanLocalPeerState()
|
||||||
|
|
||||||
|
assert.Equal(t, emptyLocalPeerState, status.localPeer, "local peer status should be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateSignalState(t *testing.T) {
|
||||||
|
url := "https://signal"
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
connected bool
|
||||||
|
want SignalState
|
||||||
|
}{
|
||||||
|
{"should mark as connected", true, SignalState{
|
||||||
|
|
||||||
|
URL: url,
|
||||||
|
Connected: true,
|
||||||
|
}},
|
||||||
|
{"should mark as disconnected", false, SignalState{
|
||||||
|
URL: url,
|
||||||
|
Connected: false,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
status := NewRecorder()
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
if test.connected {
|
||||||
|
status.MarkSignalConnected(url)
|
||||||
|
} else {
|
||||||
|
status.MarkSignalDisconnected(url)
|
||||||
|
}
|
||||||
|
assert.Equal(t, test.want, status.signal, "signal status should be equal")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateManagementState(t *testing.T) {
|
||||||
|
url := "https://management"
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
connected bool
|
||||||
|
want ManagementState
|
||||||
|
}{
|
||||||
|
{"should mark as connected", true, ManagementState{
|
||||||
|
|
||||||
|
URL: url,
|
||||||
|
Connected: true,
|
||||||
|
}},
|
||||||
|
{"should mark as disconnected", false, ManagementState{
|
||||||
|
URL: url,
|
||||||
|
Connected: false,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
status := NewRecorder()
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
if test.connected {
|
||||||
|
status.MarkManagementConnected(url)
|
||||||
|
} else {
|
||||||
|
status.MarkManagementDisconnected(url)
|
||||||
|
}
|
||||||
|
assert.Equal(t, test.want, status.management, "signal status should be equal")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFullStatus(t *testing.T) {
|
||||||
|
key1 := "abc"
|
||||||
|
key2 := "def"
|
||||||
|
managementState := ManagementState{
|
||||||
|
URL: "https://signal",
|
||||||
|
Connected: true,
|
||||||
|
}
|
||||||
|
signalState := SignalState{
|
||||||
|
URL: "https://signal",
|
||||||
|
Connected: true,
|
||||||
|
}
|
||||||
|
peerState1 := PeerState{
|
||||||
|
PubKey: key1,
|
||||||
|
}
|
||||||
|
|
||||||
|
peerState2 := PeerState{
|
||||||
|
PubKey: key2,
|
||||||
|
}
|
||||||
|
|
||||||
|
status := NewRecorder()
|
||||||
|
|
||||||
|
status.management = managementState
|
||||||
|
status.signal = signalState
|
||||||
|
status.peers[key1] = peerState1
|
||||||
|
status.peers[key2] = peerState2
|
||||||
|
|
||||||
|
fullStatus := status.GetFullStatus()
|
||||||
|
|
||||||
|
assert.Equal(t, managementState, fullStatus.ManagementState, "management status should be equal")
|
||||||
|
assert.Equal(t, signalState, fullStatus.SignalState, "signal status should be equal")
|
||||||
|
assert.ElementsMatch(t, []PeerState{peerState1, peerState2}, fullStatus.Peers, "peers states should match")
|
||||||
|
}
|
||||||
@@ -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,3 +1,7 @@
|
|||||||
|
//go:build !(linux && 386)
|
||||||
|
// +build !linux !386
|
||||||
|
|
||||||
|
// skipping linux 32 bits build and tests
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
104
docs/README.md
104
docs/README.md
@@ -1,104 +0,0 @@
|
|||||||
### Table of contents
|
|
||||||
|
|
||||||
* [About Netbird](#about-netbird)
|
|
||||||
* [Why Wireguard with Netbird?](#why-wireguard-with-netbird)
|
|
||||||
* [Netbird vs. Traditional VPN](#netbird-vs-traditional-vpn)
|
|
||||||
* [High-level technology overview](#high-level-technology-overview)
|
|
||||||
* [Getting started](#getting-started)
|
|
||||||
|
|
||||||
### About Netbird
|
|
||||||
|
|
||||||
Netbird is an open-source VPN platform built on top of [WireGuard®](https://www.wireguard.com/) making it easy to create secure private networks for your organization or home.
|
|
||||||
|
|
||||||
It requires zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, vpn gateways, and so forth.
|
|
||||||
|
|
||||||
There is no centralized VPN server with Netbird - your computers, devices, machines, and servers connect to each other directly over a fast encrypted tunnel.
|
|
||||||
|
|
||||||
It literally takes less than 5 minutes to provision a secure peer-to-peer VPN with Netbird. Check our [Quickstart Guide Video](https://www.youtube.com/watch?v=cWTsGUJAUaU) to see the setup in action.
|
|
||||||
|
|
||||||
### Why Wireguard with Netbird?
|
|
||||||
|
|
||||||
WireGuard is a modern and extremely fast VPN tunnel utilizing state-of-the-art [cryptography](https://www.wireguard.com/protocol/)
|
|
||||||
and Netbird uses Wireguard to establish a secure tunnel between machines.
|
|
||||||
|
|
||||||
Built with simplicity in mind, Wireguard ensures that traffic between two machines is encrypted and flowing, however, it requires a few things to be done beforehand.
|
|
||||||
|
|
||||||
First, in order to connect, the machines have to be configured.
|
|
||||||
On each machine, you need to generate private and public keys and prepare a WireGuard configuration file.
|
|
||||||
The configuration also includes a private IP address that should be unique per machine.
|
|
||||||
|
|
||||||
Secondly, to accept the incoming traffic, the machines have to trust each other.
|
|
||||||
The generated public keys have to be pre-shared on the machines.
|
|
||||||
This works similarly to SSH with its authorised_keys file.
|
|
||||||
|
|
||||||
Lastly, the connectivity between the machines has to be ensured.
|
|
||||||
To make machines reach one another, you are required to set a WireGuard endpoint property which indicates the IP address and port of the remote machine to connect to.
|
|
||||||
On many occasions, machines are hidden behind firewalls and NAT devices,
|
|
||||||
meaning that you may need to configure a port forwarding or open holes in your firewall to ensure the machines are reachable.
|
|
||||||
|
|
||||||
The undertakings mentioned above might not be complicated if you have just a few machines, but the complexity grows as the number of machines increases.
|
|
||||||
|
|
||||||
Netbird simplifies the setup by automatically generating private and public keys, assigning unique private IP addresses, and takes care of sharing public keys between the machines.
|
|
||||||
It is worth mentioning that the private key never leaves the machine.
|
|
||||||
So only the machine that owns the key can decrypt traffic addressed to it.
|
|
||||||
The same applies also to the relayed traffic mentioned below.
|
|
||||||
|
|
||||||
Furthermore, Netbird ensures connectivity by leveraging advanced [NAT traversal techniques](https://en.wikipedia.org/wiki/NAT_traversal)
|
|
||||||
and removing the necessity of port forwarding, opening holes in the firewall, and having a public static IP address.
|
|
||||||
In cases when a direct peer-to-peer connection isn't possible, all traffic is relayed securely between peers.
|
|
||||||
Netbird also monitors the connection health and restarts broken connections.
|
|
||||||
|
|
||||||
There are a few more things that we are working on to make secure private networks simple. A few examples are ACLs, MFA and activity monitoring.
|
|
||||||
|
|
||||||
Check out the WireGuard [Quick Start](https://www.wireguard.com/quickstart/) guide to learn more about configuring "plain" WireGuard without Netbird.
|
|
||||||
|
|
||||||
### Netbird vs. Traditional VPN
|
|
||||||
|
|
||||||
In the traditional VPN model, everything converges on a centralized, protected network where all the clients are connecting to a central VPN server.
|
|
||||||
|
|
||||||
An increasing amount of connections can easily overload the VPN server.
|
|
||||||
Even a short downtime of a server can cause expensive system disruptions, and a remote team's inability to work.
|
|
||||||
|
|
||||||
Centralized VPNs imply all the traffic going through the central server causing network delays and increased traffic usage.
|
|
||||||
|
|
||||||
Such systems require an experienced team to set up and maintain.
|
|
||||||
Configuring firewalls, setting up NATs, SSO integration, and managing access control lists can be a nightmare.
|
|
||||||
|
|
||||||
Traditional centralized VPNs are often compared to a [castle-and-moat](https://en.wikipedia.org/wiki/Moat) model
|
|
||||||
in which once accessed, user is trusted and can access critical infrastructure and resources without any restrictions.
|
|
||||||
|
|
||||||
Netbird decentralizes networks using direct point-to-point connections, as opposed to traditional models.
|
|
||||||
Consequently, network performance is increased since traffic flows directly between the machines bypassing VPN servers or gateways.
|
|
||||||
To achieve this, Netbird client applications employ signalling servers to find other machines and negotiate connections.
|
|
||||||
These are similar to the signaling servers used in [WebRTC](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling#the_signaling_server)
|
|
||||||
|
|
||||||
Thanks to [NAT traversal techniques](https://en.wikipedia.org/wiki/NAT_traversal),
|
|
||||||
outlined in the [Why not just Wireguard?](#why-wireguard-with-netbird) section above,
|
|
||||||
Netbird installation doesn't require complex network and firewall configuration.
|
|
||||||
It just works, minimising the maintenance effort.
|
|
||||||
|
|
||||||
Finally, each machine or device in the Netbird network verifies incoming connections accepting only the trusted ones.
|
|
||||||
This is ensured by Wireguard's [Crypto Routing concept](https://www.wireguard.com/#cryptokey-routing).
|
|
||||||
|
|
||||||
### High-level technology overview
|
|
||||||
In essence, Netbird is an open source platform consisting of a collection of systems, responsible for handling peer-to-peer connections, tunneling and network management (IP, keys, ACLs, etc).
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img src="media/high-level-dia.png" alt="high-level-dia" width="781"/>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
Netbird uses open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [Coturn](https://github.com/coturn/coturn),
|
|
||||||
and [software](https://github.com/netbirdio/netbird) developed by Netbird authors to make it all work together.
|
|
||||||
|
|
||||||
To learn more about Netbird architecture, please refer to the [architecture section](../docs/architecture.md).
|
|
||||||
|
|
||||||
### Getting Started
|
|
||||||
|
|
||||||
There are 2 ways of getting started with Netbird:
|
|
||||||
- use Cloud Managed version
|
|
||||||
- self-hosting
|
|
||||||
|
|
||||||
We recommend starting with the cloud managed version hosted at [app.netbird.io](https://app.netbird.io) - the quickest way to get familiar with the system.
|
|
||||||
See [Quickstart Guide](../docs/quickstart.md) for instructions.
|
|
||||||
|
|
||||||
If you don't want to use the managed version, check out our [Self-hosting Guide](../docs/self-hosting.md).
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
### Architecture
|
|
||||||
TODO
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.9 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
@@ -1,41 +0,0 @@
|
|||||||
## Quickstart guide (Cloud Managed version)
|
|
||||||
Step-by-step video guide on YouTube:
|
|
||||||
|
|
||||||
[](https://youtu.be/cWTsGUJAUaU "Netbird - secure private network in less than 5 minutes")
|
|
||||||
|
|
||||||
This guide describes how to create secure VPN and connect 2 machines peer-to-peer.
|
|
||||||
|
|
||||||
One machine is a Raspberry Pi Compute Module 4 hosted at home (Peer A), and the other one is a regular Ubuntu server running in the Data Center (Peer B).
|
|
||||||
Both machines are running Linux (Raspbian and Ubuntu respectively), but you could also use Mac or Windows operating systems.
|
|
||||||
|
|
||||||
1. Sign-up at [https://app.netbird.io/](https://app.netbird.io/)
|
|
||||||
|
|
||||||
You can use your email and password to sign-up or any available social login option (e.g., GitHub account)
|
|
||||||
|
|
||||||
<img src="media/auth.png" alt="auth" width="350"/>
|
|
||||||
|
|
||||||
2. After a successful login you will be redirected to the ```Peers``` screen which is empty because you don't have any peers yet.
|
|
||||||
|
|
||||||
Click ```Add peer``` to add a new machine.
|
|
||||||
|
|
||||||
<img src="media/empty-peers.png" alt="empty-peers" width="700"/>
|
|
||||||
|
|
||||||
3. Choose a setup key which will be used to associate your new machine with your account (in our case it is ```Default key```).
|
|
||||||
|
|
||||||
Choose your machine operating system (in our case it is ```Linux```) and proceed with the installation steps on the machine.
|
|
||||||
|
|
||||||
<img src="media/add-peer.png" alt="add-peer" width="700"/>
|
|
||||||
|
|
||||||
4. Repeat #3 for the 2nd machine.
|
|
||||||
5. Return to ```Peers``` and you should notice 2 new machines with status ```Connected```
|
|
||||||
|
|
||||||
<img src="media/peers.png" alt="peers" width="700"/>
|
|
||||||
|
|
||||||
6. To test the connection you could try pinging devices:
|
|
||||||
|
|
||||||
On Peer A:
|
|
||||||
```ping 100.64.0.2```
|
|
||||||
|
|
||||||
On Peer B:
|
|
||||||
```ping 100.64.0.1```
|
|
||||||
7. Done! You now have a secure peer-to-peer VPN configured.
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
### Self-hosting
|
|
||||||
Netbird is an open-source platform that can be self-hosted on your servers.
|
|
||||||
|
|
||||||
It relies on components developed by Netbird Authors [Management Service](https://github.com/netbirdio/netbird/tree/main/management), [Management UI Dashboard](https://github.com/netbirdio/dashboard), [Signal Service](https://github.com/netbirdio/netbird/tree/main/signal),
|
|
||||||
a 3rd party open-source STUN/TURN service [Coturn](https://github.com/coturn/coturn) and a 3rd party service [Auth0](https://auth0.com/).
|
|
||||||
|
|
||||||
All the components can be self-hosted except for the Auth0 service.
|
|
||||||
We chose Auth0 to "outsource" the user management part of the platform because we believe that implementing a proper user auth requires significant amount of time to make it right.
|
|
||||||
We focused on connectivity instead. It also offers an always free plan that should be ok for most users as its limits are high enough for most teams.
|
|
||||||
|
|
||||||
If you would like to learn more about the architecture please refer to the [Netbird Architecture section](architecture.md).
|
|
||||||
|
|
||||||
### Step-by-step video guide on YouTube:
|
|
||||||
|
|
||||||
[](https://youtu.be/Ofpgx5WhT0k "Netbird Self-Hosting Guide")
|
|
||||||
|
|
||||||
### Requirements
|
|
||||||
|
|
||||||
- Virtual machine offered by any cloud provider (e.g., AWS, DigitalOcean, Hetzner, Google Cloud, Azure ...).
|
|
||||||
- Any Unix OS.
|
|
||||||
- Docker Compose installed (see [Install Docker Compose](https://docs.docker.com/compose/install/)).
|
|
||||||
- Domain name pointing to the public IP address of your server.
|
|
||||||
- Netbird Open ports ```443, 33071, 33073, 10000``` (Dashboard, Management HTTP API, Management gRpc API, Signal gRpc) on your server.
|
|
||||||
- Coturn is used for relay using the STUN/TURN protocols. It requires a listening port, ```UDP 3478```, and range of ports,```UDP 49152-65535```, for dynamic relay connections. These are set as defaults in [setup file](https://github.com/netbirdio/netbird/blob/main/infrastructure_files/setup.env#L34), but can be configured to your requirements.
|
|
||||||
- Maybe a cup of coffee or tea :)
|
|
||||||
|
|
||||||
### Step-by-step guide
|
|
||||||
|
|
||||||
For this tutorial we will be using domain ```test.netbird.io``` which points to our Ubuntu 20.04 machine hosted at Hetzner.
|
|
||||||
|
|
||||||
1. Create Auth0 account at [auth0.com](https://auth0.com/).
|
|
||||||
2. Login to your server, clone Netbird repository:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/netbirdio/netbird.git netbird/
|
|
||||||
```
|
|
||||||
|
|
||||||
and switch to the ```netbird/infrastructure_files/``` folder that contains docker compose file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd netbird/infrastructure_files/
|
|
||||||
```
|
|
||||||
3. Prepare configuration files.
|
|
||||||
|
|
||||||
To simplify the setup we have prepared a script to substitute required properties in the [turnserver.conf.tmpl](../infrastructure_files/turnserver.conf.tmpl),[docker-compose.yml.tmpl](../infrastructure_files/docker-compose.yml.tmpl) and [management.json.tmpl](../infrastructure_files/management.json.tmpl) files.
|
|
||||||
|
|
||||||
The [setup.env](../infrastructure_files/setup.env) file contains the following properties that have to be filled:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Dashboard domain. e.g. app.mydomain.com
|
|
||||||
NETBIRD_DOMAIN=""
|
|
||||||
# e.g. dev-24vkclam.us.auth0.com
|
|
||||||
NETBIRD_AUTH0_DOMAIN=""
|
|
||||||
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
|
|
||||||
NETBIRD_AUTH0_CLIENT_ID=""
|
|
||||||
# e.g. https://app.mydomain.com/ or https://app.mydomain.com,
|
|
||||||
# Make sure you used the exact same value for Identifier
|
|
||||||
# you used when creating your Auth0 API
|
|
||||||
NETBIRD_AUTH0_AUDIENCE=""
|
|
||||||
# e.g. hello@mydomain.com
|
|
||||||
NETBIRD_LETSENCRYPT_EMAIL=""
|
|
||||||
```
|
|
||||||
> Other options are available, but they are automatically updated.
|
|
||||||
|
|
||||||
Please follow the steps to get the values.
|
|
||||||
|
|
||||||
4. Configure ```NETBIRD_AUTH0_DOMAIN``` ```NETBIRD_AUTH0_CLIENT_ID``` ```NETBIRD_AUTH0_AUDIENCE``` properties.
|
|
||||||
|
|
||||||
* To obtain these, please use [Auth0 React SDK Guide](https://auth0.com/docs/quickstart/spa/react/01-login#configure-auth0) up until "Install the Auth0 React SDK".
|
|
||||||
|
|
||||||
:grey_exclamation: Use ```https://YOUR DOMAIN``` as ````Allowed Callback URLs````, ```Allowed Logout URLs```, ```Allowed Web Origins``` and ```Allowed Origins (CORS)```
|
|
||||||
* set the variables in the ```setup.env```
|
|
||||||
5. Configure ```NETBIRD_AUTH0_AUDIENCE``` property.
|
|
||||||
|
|
||||||
* Check [Auth0 Golang API Guide](https://auth0.com/docs/quickstart/backend/golang) to obtain AuthAudience.
|
|
||||||
* set the property in the ```setup.env``` file.
|
|
||||||
6. Configure ```NETBIRD_LETSENCRYPT_EMAIL``` property.
|
|
||||||
|
|
||||||
This can be any email address. [Let's Encrypt](https://letsencrypt.org/) will create an account while generating a new certificate.
|
|
||||||
|
|
||||||
7. Make sure all the properties set in the ```setup.env``` file and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./configure.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This will export all the properties as environment variables and generate ```docker-compose.yml``` and ```management.json``` files substituting required variables.
|
|
||||||
|
|
||||||
8. Run docker compose:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
9. Optionally check the logs by running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose logs signal
|
|
||||||
docker-compose logs management
|
|
||||||
docker-compose logs coturn
|
|
||||||
docker-compose logs dashboard
|
|
||||||
|
|
||||||
10. Once the server is running, you can access the dashboard by https://$NETBIRD_DOMAIN
|
|
||||||
11. Adding a peer will require you to enter the management URL by following the steps in the page https://$NETBIRD_DOMAIN/add-peer and in the 3rd step:
|
|
||||||
```shell
|
|
||||||
sudo netbird up --setup-key <PASTE-SETUP-KEY> --management-url https://$NETBIRD_DOMAIN:33073
|
|
||||||
```
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
10
go.mod
10
go.mod
@@ -18,7 +18,7 @@ require (
|
|||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/vishvananda/netlink v1.1.0
|
github.com/vishvananda/netlink v1.1.0
|
||||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9
|
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9
|
||||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a
|
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434
|
golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.1
|
golang.zx2c4.com/wireguard/windows v0.5.1
|
||||||
@@ -30,18 +30,23 @@ 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/creack/pty v1.1.18
|
||||||
github.com/eko/gocache/v2 v2.3.1
|
github.com/eko/gocache/v2 v2.3.1
|
||||||
github.com/getlantern/systray v1.2.1
|
github.com/getlantern/systray v1.2.1
|
||||||
|
github.com/gliderlabs/ssh v0.3.4
|
||||||
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.7.1
|
||||||
|
golang.org/x/net v0.0.0-20220513224357-95641704303c
|
||||||
|
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v0.4.1 // indirect
|
github.com/BurntSushi/toml v0.4.1 // indirect
|
||||||
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
|
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
|
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
@@ -92,7 +97,6 @@ require (
|
|||||||
github.com/yuin/goldmark v1.4.1 // indirect
|
github.com/yuin/goldmark v1.4.1 // 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
|
||||||
@@ -110,5 +114,3 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/pion/ice/v2 => github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb
|
replace github.com/pion/ice/v2 => github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb
|
||||||
|
|
||||||
//replace github.com/eko/gocache/v3 => /home/braginini/Documents/projects/my/wiretrustee/gocache
|
|
||||||
|
|||||||
13
go.sum
13
go.sum
@@ -69,6 +69,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
|||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI=
|
github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
@@ -118,6 +120,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
|||||||
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=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||||
|
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -178,6 +182,8 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H
|
|||||||
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=
|
||||||
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
|
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
|
||||||
|
github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw=
|
||||||
|
github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914=
|
||||||
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76wBquMVETx7uveQD9MCIQoU=
|
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76wBquMVETx7uveQD9MCIQoU=
|
||||||
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
|
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
@@ -644,6 +650,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/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-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=
|
||||||
@@ -889,10 +896,12 @@ 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-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY=
|
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU=
|
||||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/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=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
|
||||||
|
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|||||||
@@ -33,3 +33,8 @@ func (w *WGIface) assignAddr() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WireguardModExists check if we can load wireguard mod (linux only)
|
||||||
|
func WireguardModExists() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type NativeLink struct {
|
|||||||
Link *netlink.Link
|
Link *netlink.Link
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WireguardModExists check if we can load wireguard mod (linux only)
|
||||||
func WireguardModExists() bool {
|
func WireguardModExists() bool {
|
||||||
link := newWGLink("mustnotexist")
|
link := newWGLink("mustnotexist")
|
||||||
|
|
||||||
|
|||||||
@@ -57,3 +57,8 @@ func (w *WGIface) UpdateAddr(newAddr string) error {
|
|||||||
w.Address = addr
|
w.Address = addr
|
||||||
return w.assignAddr(luid)
|
return w.assignAddr(luid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WireguardModExists check if we can load wireguard mod (linux only)
|
||||||
|
func WireguardModExists() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +1,4 @@
|
|||||||
# Dashboard domain and auth0 configuration
|
## Most settings are being done automatically with the sourced variables from setup.env, but you can edit if you need some customization
|
||||||
|
|
||||||
# Dashboard domain. e.g. app.mydomain.com
|
|
||||||
NETBIRD_DOMAIN=""
|
|
||||||
# e.g. dev-24vkclam.us.auth0.com
|
|
||||||
NETBIRD_AUTH0_DOMAIN=""
|
|
||||||
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
|
|
||||||
NETBIRD_AUTH0_CLIENT_ID=""
|
|
||||||
# e.g. https://app.mydomain.com/ or https://app.mydomain.com,
|
|
||||||
# Make sure you used the exact same value for Identifier
|
|
||||||
# you used when creating your Auth0 API
|
|
||||||
NETBIRD_AUTH0_AUDIENCE=""
|
|
||||||
# e.g. hello@mydomain.com
|
|
||||||
NETBIRD_LETSENCRYPT_EMAIL=""
|
|
||||||
|
|
||||||
## From this point, most settings are being done automatically, but you can edit if you need some customization
|
|
||||||
|
|
||||||
# Management API
|
# Management API
|
||||||
|
|
||||||
@@ -24,7 +9,7 @@ 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
|
# Management GRPC API endpoint address, used by the hosts to register
|
||||||
NETBIRD_MGMT_GRPC_API_ENDPOINT=https://$NETBIRD_DOMAIN:NETBIRD_MGMT_GRPC_API_PORT
|
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.
|
||||||
@@ -1,10 +1,32 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
source setup.env
|
source setup.env
|
||||||
|
source base.setup.env
|
||||||
|
|
||||||
|
if ! which envsubst > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo "envsubst is needed to run this script"
|
||||||
|
if [[ $(uname) == "Darwin" ]]
|
||||||
|
then
|
||||||
|
echo "you can install it with homebrew (https://brew.sh):"
|
||||||
|
echo "brew install gettext"
|
||||||
|
else
|
||||||
|
if which apt-get > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo "you can install it by running"
|
||||||
|
echo "apt-get update && apt-get install gettext-base"
|
||||||
|
else
|
||||||
|
echo "you can install it by installing the package gettext with your package manager"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "x-$NETBIRD_DOMAIN" == "x-" ]]
|
if [[ "x-$NETBIRD_DOMAIN" == "x-" ]]
|
||||||
then
|
then
|
||||||
echo NETBIRD_DOMAIN is not set, please update your setup.env file
|
echo NETBIRD_DOMAIN is not set, please update your setup.env file
|
||||||
|
echo If you are migrating from old versions, you migh need to update your variables prefixes from
|
||||||
|
echo WIRETRUSTEE_.. TO NETBIRD_
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,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"]
|
||||||
@@ -42,8 +42,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- $NETBIRD_MGMT_GRPC_API_PORT:33073 #gRPC port
|
- $NETBIRD_MGMT_GRPC_API_PORT:33073 #gRPC port
|
||||||
- $NETBIRD_MGMT_API_PORT:33071 #API port
|
- $NETBIRD_MGMT_API_PORT:33071 #API port
|
||||||
# # port and command for Let's Encrypt validation
|
# # 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"]
|
||||||
# Coturn
|
# Coturn
|
||||||
coturn:
|
coturn:
|
||||||
|
|||||||
16
infrastructure_files/setup.env.example
Normal file
16
infrastructure_files/setup.env.example
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
## 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
|
||||||
|
NETBIRD_DOMAIN=""
|
||||||
|
# e.g. dev-24vkclam.us.auth0.com
|
||||||
|
NETBIRD_AUTH0_DOMAIN=""
|
||||||
|
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
|
||||||
|
NETBIRD_AUTH0_CLIENT_ID=""
|
||||||
|
# e.g. https://app.mydomain.com/ or https://app.mydomain.com,
|
||||||
|
# Make sure you used the exact same value for Identifier
|
||||||
|
# you used when creating your Auth0 API
|
||||||
|
NETBIRD_AUTH0_AUDIENCE=""
|
||||||
|
# e.g. hello@mydomain.com
|
||||||
|
NETBIRD_LETSENCRYPT_EMAIL=""
|
||||||
16
infrastructure_files/tests/setup.env
Normal file
16
infrastructure_files/tests/setup.env
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
## 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
|
||||||
|
NETBIRD_DOMAIN="localhost"
|
||||||
|
# e.g. dev-24vkclam.us.auth0.com
|
||||||
|
NETBIRD_AUTH0_DOMAIN=$CI_NETBIRD_AUTH0_DOMAIN
|
||||||
|
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
|
||||||
|
NETBIRD_AUTH0_CLIENT_ID=$CI_NETBIRD_AUTH0_CLIENT_ID
|
||||||
|
# e.g. https://app.mydomain.com/ or https://app.mydomain.com,
|
||||||
|
# Make sure you used the exact same value for Identifier
|
||||||
|
# you used when creating your Auth0 API
|
||||||
|
NETBIRD_AUTH0_AUDIENCE=$CI_NETBIRD_AUTH0_AUDIENCE
|
||||||
|
# e.g. hello@mydomain.com
|
||||||
|
NETBIRD_LETSENCRYPT_EMAIL=""
|
||||||
@@ -12,7 +12,7 @@ type Client interface {
|
|||||||
io.Closer
|
io.Closer
|
||||||
Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
||||||
GetServerPublicKey() (*wgtypes.Key, error)
|
GetServerPublicKey() (*wgtypes.Key, error)
|
||||||
Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info) (*proto.LoginResponse, error)
|
Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||||
Login(serverKey wgtypes.Key, sysInfo *system.Info) (*proto.LoginResponse, error)
|
Login(serverKey wgtypes.Key, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||||
GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
|
GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ func TestClient_LoginUnregistered_ShouldThrow_401(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
sysInfo := system.GetInfo(context.TODO())
|
sysInfo := system.GetInfo(context.TODO())
|
||||||
_, err = client.Login(*key, sysInfo)
|
_, err = client.Login(*key, sysInfo, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expecting err on unregistered login, got nil")
|
t.Error("expecting err on unregistered login, got nil")
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,7 @@ func TestClient_LoginRegistered(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
info := system.GetInfo(context.TODO())
|
info := system.GetInfo(context.TODO())
|
||||||
resp, err := client.Register(*key, ValidKey, "", info)
|
resp, err := client.Register(*key, ValidKey, "", info, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -216,7 +216,7 @@ func TestClient_Sync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info := system.GetInfo(context.TODO())
|
info := system.GetInfo(context.TODO())
|
||||||
_, err = client.Register(*serverKey, ValidKey, "", info)
|
_, err = client.Register(*serverKey, ValidKey, "", info, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -232,7 +232,7 @@ func TestClient_Sync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info = system.GetInfo(context.TODO())
|
info = system.GetInfo(context.TODO())
|
||||||
_, err = remoteClient.Register(*serverKey, ValidKey, "", info)
|
_, err = remoteClient.Register(*serverKey, ValidKey, "", info, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -330,7 +330,7 @@ func Test_SystemMetaDataFromClient(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info := system.GetInfo(context.TODO())
|
info := system.GetInfo(context.TODO())
|
||||||
_, err = testClient.Register(*key, ValidKey, "", info)
|
_, err = testClient.Register(*key, ValidKey, "", info, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error while trying to register client: %v", err)
|
t.Errorf("error while trying to register client: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE
|
|||||||
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
mgmCtx, cancel := context.WithTimeout(ctx, time.Second*3)
|
mgmCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
conn, err := grpc.DialContext(
|
conn, err := grpc.DialContext(
|
||||||
mgmCtx,
|
mgmCtx,
|
||||||
@@ -72,10 +72,10 @@ func (c *GrpcClient) Close() error {
|
|||||||
func defaultBackoff(ctx context.Context) backoff.BackOff {
|
func defaultBackoff(ctx context.Context) backoff.BackOff {
|
||||||
return backoff.WithContext(&backoff.ExponentialBackOff{
|
return backoff.WithContext(&backoff.ExponentialBackOff{
|
||||||
InitialInterval: 800 * time.Millisecond,
|
InitialInterval: 800 * time.Millisecond,
|
||||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
RandomizationFactor: 1,
|
||||||
Multiplier: backoff.DefaultMultiplier,
|
Multiplier: 1.7,
|
||||||
MaxInterval: 10 * time.Second,
|
MaxInterval: 10 * time.Second,
|
||||||
MaxElapsedTime: 12 * time.Hour, // stop after 12 hours of trying, the error will be propagated to the general retry of the client
|
MaxElapsedTime: 3 * 30 * 24 * time.Hour, // 3 months
|
||||||
Stop: backoff.Stop,
|
Stop: backoff.Stop,
|
||||||
Clock: backoff.SystemClock,
|
Clock: backoff.SystemClock,
|
||||||
}, ctx)
|
}, ctx)
|
||||||
@@ -95,20 +95,26 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
|||||||
operation := func() error {
|
operation := func() error {
|
||||||
log.Debugf("management connection state %v", c.conn.GetState())
|
log.Debugf("management connection state %v", c.conn.GetState())
|
||||||
|
|
||||||
if !c.ready() {
|
connState := c.conn.GetState()
|
||||||
return fmt.Errorf("no connection to management")
|
if connState == connectivity.Shutdown {
|
||||||
|
return backoff.Permanent(fmt.Errorf("connection to management has been shut down"))
|
||||||
|
} else if !(connState == connectivity.Ready || connState == connectivity.Idle) {
|
||||||
|
c.conn.WaitForStateChange(c.ctx, connState)
|
||||||
|
return fmt.Errorf("connection to management is not ready and in %s state", connState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo we already have it since we did the Login, maybe cache it locally?
|
|
||||||
serverPubKey, err := c.GetServerPublicKey()
|
serverPubKey, err := c.GetServerPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed getting Management Service public key: %s", err)
|
log.Debugf("failed getting Management Service public key: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stream, err := c.connectToStream(*serverPubKey)
|
stream, err := c.connectToStream(*serverPubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("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 {
|
||||||
|
return backoff.Permanent(err) // unrecoverable error, propagate to the upper layer
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,10 +123,13 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
|||||||
// blocking until error
|
// blocking until error
|
||||||
err = c.receiveEvents(stream, *serverPubKey, msgHandler)
|
err = c.receiveEvents(stream, *serverPubKey, msgHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.InvalidArgument || s.Code() == codes.PermissionDenied) {
|
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
||||||
return backoff.Permanent(err)
|
return backoff.Permanent(err) // unrecoverable error, propagate to the upper layer
|
||||||
}
|
}
|
||||||
|
// we need this reset because after a successful connection and a consequent error, backoff lib doesn't
|
||||||
|
// reset times and next try will start with a long delay
|
||||||
backOff.Reset()
|
backOff.Reset()
|
||||||
|
log.Warnf("disconnected from the Management service but will retry silently. Reason: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +138,7 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
|||||||
|
|
||||||
err := backoff.Retry(operation, backOff)
|
err := backoff.Retry(operation, backOff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("exiting Management Service connection retry loop due to Permanent error: %s", err)
|
log.Warnf("exiting the Management service connection retry loop due to the unrecoverable error: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,11 +165,11 @@ func (c *GrpcClient) receiveEvents(stream proto.ManagementService_SyncClient, se
|
|||||||
for {
|
for {
|
||||||
update, err := stream.Recv()
|
update, err := stream.Recv()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
log.Errorf("Management stream has been closed by server: %s", err)
|
log.Debugf("Management stream has been closed by server: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("disconnected from Management Service sync stream: %v", err)
|
log.Debugf("disconnected from Management Service sync stream: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,13 +189,13 @@ func (c *GrpcClient) receiveEvents(stream proto.ManagementService_SyncClient, se
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServerPublicKey returns server Wireguard public key (used later for encrypting messages sent to the server)
|
// GetServerPublicKey returns server's WireGuard public key (used later for encrypting messages sent to the server)
|
||||||
func (c *GrpcClient) GetServerPublicKey() (*wgtypes.Key, error) {
|
func (c *GrpcClient) GetServerPublicKey() (*wgtypes.Key, error) {
|
||||||
if !c.ready() {
|
if !c.ready() {
|
||||||
return nil, fmt.Errorf("no connection to management")
|
return nil, fmt.Errorf("no connection to management")
|
||||||
}
|
}
|
||||||
|
|
||||||
mgmCtx, cancel := context.WithTimeout(c.ctx, time.Second*2)
|
mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
resp, err := c.realClient.GetServerKey(mgmCtx, &proto.Empty{})
|
resp, err := c.realClient.GetServerKey(mgmCtx, &proto.Empty{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -210,7 +219,7 @@ func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*pro
|
|||||||
log.Errorf("failed to encrypt message: %s", err)
|
log.Errorf("failed to encrypt message: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mgmCtx, cancel := context.WithTimeout(c.ctx, time.Second*2)
|
mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
resp, err := c.realClient.Login(mgmCtx, &proto.EncryptedMessage{
|
resp, err := c.realClient.Login(mgmCtx, &proto.EncryptedMessage{
|
||||||
WgPubKey: c.key.PublicKey().String(),
|
WgPubKey: c.key.PublicKey().String(),
|
||||||
@@ -233,13 +242,21 @@ func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*pro
|
|||||||
// Register registers peer on Management Server. It actually calls a Login endpoint with a provided setup key
|
// Register registers peer on Management Server. It actually calls a Login endpoint with a provided setup key
|
||||||
// Takes care of encrypting and decrypting messages.
|
// Takes care of encrypting and decrypting messages.
|
||||||
// This method will also collect system info and send it with the request (e.g. hostname, os, etc)
|
// This method will also collect system info and send it with the request (e.g. hostname, os, etc)
|
||||||
func (c *GrpcClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info) (*proto.LoginResponse, error) {
|
func (c *GrpcClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info, pubSSHKey []byte) (*proto.LoginResponse, error) {
|
||||||
return c.login(serverKey, &proto.LoginRequest{SetupKey: setupKey, Meta: infoToMetaData(sysInfo), JwtToken: jwtToken})
|
keys := &proto.PeerKeys{
|
||||||
|
SshPubKey: pubSSHKey,
|
||||||
|
WgPubKey: []byte(c.key.PublicKey().String()),
|
||||||
|
}
|
||||||
|
return c.login(serverKey, &proto.LoginRequest{SetupKey: setupKey, Meta: infoToMetaData(sysInfo), JwtToken: jwtToken, PeerKeys: keys})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login attempts login to Management Server. Takes care of encrypting and decrypting messages.
|
// Login attempts login to Management Server. Takes care of encrypting and decrypting messages.
|
||||||
func (c *GrpcClient) Login(serverKey wgtypes.Key, sysInfo *system.Info) (*proto.LoginResponse, error) {
|
func (c *GrpcClient) Login(serverKey wgtypes.Key, sysInfo *system.Info, pubSSHKey []byte) (*proto.LoginResponse, error) {
|
||||||
return c.login(serverKey, &proto.LoginRequest{Meta: infoToMetaData(sysInfo)})
|
keys := &proto.PeerKeys{
|
||||||
|
SshPubKey: pubSSHKey,
|
||||||
|
WgPubKey: []byte(c.key.PublicKey().String()),
|
||||||
|
}
|
||||||
|
return c.login(serverKey, &proto.LoginRequest{Meta: infoToMetaData(sysInfo), PeerKeys: keys})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDeviceAuthorizationFlow returns a device authorization flow information.
|
// GetDeviceAuthorizationFlow returns a device authorization flow information.
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ type MockClient struct {
|
|||||||
CloseFunc func() error
|
CloseFunc func() error
|
||||||
SyncFunc func(msgHandler func(msg *proto.SyncResponse) error) error
|
SyncFunc func(msgHandler func(msg *proto.SyncResponse) error) error
|
||||||
GetServerPublicKeyFunc func() (*wgtypes.Key, error)
|
GetServerPublicKeyFunc func() (*wgtypes.Key, error)
|
||||||
RegisterFunc func(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info) (*proto.LoginResponse, error)
|
RegisterFunc func(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||||
LoginFunc func(serverKey wgtypes.Key, info *system.Info) (*proto.LoginResponse, error)
|
LoginFunc func(serverKey wgtypes.Key, info *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||||
GetDeviceAuthorizationFlowFunc func(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
|
GetDeviceAuthorizationFlowFunc func(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,18 +36,18 @@ func (m *MockClient) GetServerPublicKey() (*wgtypes.Key, error) {
|
|||||||
return m.GetServerPublicKeyFunc()
|
return m.GetServerPublicKeyFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info) (*proto.LoginResponse, error) {
|
func (m *MockClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info, sshKey []byte) (*proto.LoginResponse, error) {
|
||||||
if m.RegisterFunc == nil {
|
if m.RegisterFunc == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return m.RegisterFunc(serverKey, setupKey, jwtToken, info)
|
return m.RegisterFunc(serverKey, setupKey, jwtToken, info, sshKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockClient) Login(serverKey wgtypes.Key, info *system.Info) (*proto.LoginResponse, error) {
|
func (m *MockClient) Login(serverKey wgtypes.Key, info *system.Info, sshKey []byte) (*proto.LoginResponse, error) {
|
||||||
if m.LoginFunc == nil {
|
if m.LoginFunc == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return m.LoginFunc(serverKey, info)
|
return m.LoginFunc(serverKey, info, sshKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockClient) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error) {
|
func (m *MockClient) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error) {
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
httpapi "github.com/netbirdio/netbird/management/server/http"
|
||||||
|
"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"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"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 +32,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 +57,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 +113,181 @@ 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)
|
||||||
|
|
||||||
go func() {
|
var compatListener net.Listener
|
||||||
if err = grpcServer.Serve(lis); err != nil {
|
if mgmtPort != ManagementLegacyPort {
|
||||||
log.Fatalf("failed to serve gRpc server: %v", err)
|
// 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)
|
||||||
|
|
||||||
go func() {
|
|
||||||
err = httpServer.Start()
|
|
||||||
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 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")
|
||||||
|
fmt.Println(grpcHeader)
|
||||||
|
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)
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ 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")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -71,9 +71,21 @@ message LoginRequest {
|
|||||||
PeerSystemMeta meta = 2;
|
PeerSystemMeta meta = 2;
|
||||||
// SSO token (can be empty)
|
// SSO token (can be empty)
|
||||||
string jwtToken = 3;
|
string jwtToken = 3;
|
||||||
|
// Can be absent for now.
|
||||||
|
PeerKeys peerKeys = 4;
|
||||||
|
|
||||||
|
}
|
||||||
|
// PeerKeys is additional peer info like SSH pub key and WireGuard public key.
|
||||||
|
// This message is sent on Login or register requests, or when a key rotation has to happen.
|
||||||
|
message PeerKeys {
|
||||||
|
|
||||||
|
// sshPubKey represents a public SSH key of the peer. Can be absent.
|
||||||
|
bytes sshPubKey = 1;
|
||||||
|
// wgPubKey represents a public WireGuard key of the peer. Can be absent.
|
||||||
|
bytes wgPubKey = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer machine meta data
|
// PeerSystemMeta is machine meta data like OS and version.
|
||||||
message PeerSystemMeta {
|
message PeerSystemMeta {
|
||||||
string hostname = 1;
|
string hostname = 1;
|
||||||
string goOS = 2;
|
string goOS = 2;
|
||||||
@@ -143,6 +155,9 @@ message PeerConfig {
|
|||||||
string address = 1;
|
string address = 1;
|
||||||
// Wiretrustee DNS server (a Wireguard DNS config)
|
// Wiretrustee DNS server (a Wireguard DNS config)
|
||||||
string dns = 2;
|
string dns = 2;
|
||||||
|
|
||||||
|
// SSHConfig of the peer.
|
||||||
|
SSHConfig sshConfig = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections
|
// NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections
|
||||||
@@ -172,7 +187,22 @@ message RemotePeerConfig {
|
|||||||
|
|
||||||
// Wireguard allowed IPs of a remote peer e.g. [10.30.30.1/32]
|
// Wireguard allowed IPs of a remote peer e.g. [10.30.30.1/32]
|
||||||
repeated string allowedIps = 2;
|
repeated string allowedIps = 2;
|
||||||
|
|
||||||
|
// SSHConfig is a SSH config of the remote peer. SSHConfig.sshPubKey should be ignored because peer knows it's SSH key.
|
||||||
|
SSHConfig sshConfig = 3;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSHConfig represents SSH configurations of a peer.
|
||||||
|
message SSHConfig {
|
||||||
|
// sshEnabled indicates whether a SSH server is enabled on this peer
|
||||||
|
bool sshEnabled = 1;
|
||||||
|
|
||||||
|
// sshPubKey is a SSH public key of a peer to be added to authorized_hosts.
|
||||||
|
// This property should be ignore if SSHConfig comes from PeerConfig.
|
||||||
|
bytes sshPubKey = 2;
|
||||||
|
}
|
||||||
|
|
||||||
// DeviceAuthorizationFlowRequest empty struct for future expansion
|
// DeviceAuthorizationFlowRequest empty struct for future expansion
|
||||||
message DeviceAuthorizationFlowRequest {}
|
message DeviceAuthorizationFlowRequest {}
|
||||||
// DeviceAuthorizationFlow represents Device Authorization Flow information
|
// DeviceAuthorizationFlow represents Device Authorization Flow information
|
||||||
|
|||||||
@@ -49,8 +49,10 @@ type AccountManager interface {
|
|||||||
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)
|
||||||
GetNetworkMap(peerKey string) (*NetworkMap, error)
|
GetNetworkMap(peerKey string) (*NetworkMap, error)
|
||||||
|
GetPeerNetwork(peerKey string) (*Network, error)
|
||||||
AddPeer(setupKey string, userId string, peer *Peer) (*Peer, error)
|
AddPeer(setupKey string, userId string, peer *Peer) (*Peer, error)
|
||||||
UpdatePeerMeta(peerKey string, meta PeerSystemMeta) error
|
UpdatePeerMeta(peerKey string, meta PeerSystemMeta) error
|
||||||
|
UpdatePeerSSHKey(peerKey string, sshKey string) error
|
||||||
GetUsersFromAccount(accountId string) ([]*UserInfo, error)
|
GetUsersFromAccount(accountId string) ([]*UserInfo, error)
|
||||||
GetGroup(accountId, groupID string) (*Group, error)
|
GetGroup(accountId, groupID string) (*Group, error)
|
||||||
SaveGroup(accountId string, group *Group) error
|
SaveGroup(accountId string, group *Group) error
|
||||||
@@ -65,6 +67,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultAccountManager struct {
|
type DefaultAccountManager struct {
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ 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.
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import (
|
|||||||
"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 +30,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 +50,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 +61,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,7 +76,7 @@ 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)
|
log.Debugf("Sync request from peer %s", req.WgPubKey)
|
||||||
|
|
||||||
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||||
@@ -150,7 +150,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
|
||||||
@@ -185,9 +185,15 @@ func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Pe
|
|||||||
return nil, status.Errorf(codes.InvalidArgument, "peer meta data was not provided")
|
return nil, status.Errorf(codes.InvalidArgument, "peer meta data was not provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sshKey []byte
|
||||||
|
if req.GetPeerKeys() != nil {
|
||||||
|
sshKey = req.GetPeerKeys().GetSshPubKey()
|
||||||
|
}
|
||||||
|
|
||||||
peer, err := s.accountManager.AddPeer(reqSetupKey, userId, &Peer{
|
peer, err := s.accountManager.AddPeer(reqSetupKey, userId, &Peer{
|
||||||
Key: peerKey.String(),
|
Key: peerKey.String(),
|
||||||
Name: meta.GetHostname(),
|
Name: meta.GetHostname(),
|
||||||
|
SSHKey: string(sshKey),
|
||||||
Meta: PeerSystemMeta{
|
Meta: PeerSystemMeta{
|
||||||
Hostname: meta.GetHostname(),
|
Hostname: meta.GetHostname(),
|
||||||
GoOS: meta.GetGoOS(),
|
GoOS: meta.GetGoOS(),
|
||||||
@@ -224,7 +230,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())
|
update := toSyncResponse(s.config, remotePeer, peersToSend, 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
|
||||||
@@ -239,7 +245,7 @@ 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)
|
log.Debugf("Login request from peer %s", req.WgPubKey)
|
||||||
|
|
||||||
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||||
@@ -290,10 +296,28 @@ func (s *Server) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto
|
|||||||
return nil, status.Error(codes.Internal, "internal server error")
|
return nil, status.Error(codes.Internal, "internal server error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sshKey []byte
|
||||||
|
if loginReq.GetPeerKeys() != nil {
|
||||||
|
sshKey = loginReq.GetPeerKeys().GetSshPubKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sshKey) > 0 {
|
||||||
|
err = s.accountManager.UpdatePeerSSHKey(peerKey.String(), string(sshKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
network, err := s.accountManager.GetPeerNetwork(peer.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed getting peer network on login")
|
||||||
|
}
|
||||||
|
|
||||||
// if peer has reached this point then it has logged in
|
// if peer has reached this point then it has logged in
|
||||||
loginResp := &proto.LoginResponse{
|
loginResp := &proto.LoginResponse{
|
||||||
WiretrusteeConfig: toWiretrusteeConfig(s.config, nil),
|
WiretrusteeConfig: toWiretrusteeConfig(s.config, nil),
|
||||||
PeerConfig: toPeerConfig(peer),
|
PeerConfig: toPeerConfig(peer, network),
|
||||||
}
|
}
|
||||||
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, loginResp)
|
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, loginResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -363,9 +387,11 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toPeerConfig(peer *Peer) *proto.PeerConfig {
|
func toPeerConfig(peer *Peer, network *Network) *proto.PeerConfig {
|
||||||
|
netmask, _ := network.Net.Mask.Size()
|
||||||
return &proto.PeerConfig{
|
return &proto.PeerConfig{
|
||||||
Address: fmt.Sprintf("%s/%d", peer.IP.String(), SubnetSize), // take it from the network
|
Address: fmt.Sprintf("%s/%d", peer.IP.String(), netmask), // take it from the network
|
||||||
|
SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,16 +401,16 @@ func toRemotePeerConfig(peers []*Peer) []*proto.RemotePeerConfig {
|
|||||||
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
|
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
|
||||||
WgPubKey: rPeer.Key,
|
WgPubKey: rPeer.Key,
|
||||||
AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)},
|
AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)},
|
||||||
|
SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return remotePeers
|
return remotePeers
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSyncResponse(config *Config, peer *Peer, peers []*Peer, turnCredentials *TURNCredentials, serial uint64) *proto.SyncResponse {
|
func toSyncResponse(config *Config, peer *Peer, peers []*Peer, turnCredentials *TURNCredentials, serial uint64, network *Network) *proto.SyncResponse {
|
||||||
wtConfig := toWiretrusteeConfig(config, turnCredentials)
|
wtConfig := toWiretrusteeConfig(config, turnCredentials)
|
||||||
|
|
||||||
pConfig := toPeerConfig(peer)
|
pConfig := toPeerConfig(peer, network)
|
||||||
|
|
||||||
remotePeers := toRemotePeerConfig(peers)
|
remotePeers := toRemotePeerConfig(peers)
|
||||||
|
|
||||||
@@ -403,12 +429,12 @@ func toSyncResponse(config *Config, peer *Peer, peers []*Peer, turnCredentials *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
@@ -423,7 +449,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())
|
plainResp := toSyncResponse(s.config, peer, networkMap.Peers, 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 {
|
||||||
@@ -446,7 +472,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)
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ components:
|
|||||||
required:
|
required:
|
||||||
- type
|
- type
|
||||||
- value
|
- value
|
||||||
|
ssh_enabled:
|
||||||
|
description: Indicates whether SSH server is enabled on this peer
|
||||||
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- ip
|
- ip
|
||||||
- connected
|
- connected
|
||||||
@@ -93,6 +96,7 @@ components:
|
|||||||
- version
|
- version
|
||||||
- groups
|
- groups
|
||||||
- activated_by
|
- activated_by
|
||||||
|
- ssh_enabled
|
||||||
SetupKey:
|
SetupKey:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -397,8 +401,11 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
ssh_enabled:
|
||||||
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
|
- ssh_enabled
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: A Peer object
|
description: A Peer object
|
||||||
|
|||||||
@@ -115,6 +115,9 @@ type Peer struct {
|
|||||||
// Peer's operating system and version
|
// Peer's operating system and version
|
||||||
Os string `json:"os"`
|
Os string `json:"os"`
|
||||||
|
|
||||||
|
// Indicates whether SSH server is enabled on this peer
|
||||||
|
SshEnabled bool `json:"ssh_enabled"`
|
||||||
|
|
||||||
// Peer's daemon or cli version
|
// Peer's daemon or cli version
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
@@ -265,7 +268,8 @@ type PutApiGroupsIdJSONBody struct {
|
|||||||
|
|
||||||
// PutApiPeersIdJSONBody defines parameters for PutApiPeersId.
|
// PutApiPeersIdJSONBody defines parameters for PutApiPeersId.
|
||||||
type PutApiPeersIdJSONBody struct {
|
type PutApiPeersIdJSONBody struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
SshEnabled bool `json:"ssh_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostApiRulesJSONBody defines parameters for PostApiRules.
|
// PostApiRulesJSONBody defines parameters for PostApiRules.
|
||||||
|
|||||||
@@ -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"
|
||||||
64
management/server/http/handler.go
Normal file
64
management/server/http/handler.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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(jwtMiddleware.Handler, corsMiddleware.Handler, acMiddleware.Handler)
|
||||||
|
|
||||||
|
groupsHandler := NewGroups(accountManager, authAudience)
|
||||||
|
rulesHandler := NewRules(accountManager, authAudience)
|
||||||
|
peersHandler := NewPeers(accountManager, authAudience)
|
||||||
|
keysHandler := NewSetupKeysHandler(accountManager, authAudience)
|
||||||
|
userHandler := NewUserHandler(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/setup-keys", keysHandler.GetKeys).Methods("GET", "POST", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).Methods("GET", "PUT", "OPTIONS")
|
||||||
|
|
||||||
|
apiHandler.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("POST", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).
|
||||||
|
Methods("GET", "PUT", "DELETE", "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")
|
||||||
|
|
||||||
|
return apiHandler, nil
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -34,7 +34,9 @@ func (h *Peers) updatePeer(account *server.Account, peer *server.Peer, w http.Re
|
|||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
peer, err = h.accountManager.RenamePeer(account.Id, peer.Key, req.Name)
|
|
||||||
|
update := &server.Peer{Key: peer.Key, SSHEnabled: req.SshEnabled, Name: req.Name}
|
||||||
|
peer, err = h.accountManager.UpdatePeer(account.Id, update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed updating peer %s under account %s %v", peerIp, account.Id, err)
|
log.Errorf("failed updating peer %s under account %s %v", peerIp, account.Id, err)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
@@ -133,13 +135,14 @@ func toPeerResponse(peer *server.Peer, account *server.Account) *api.Peer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &api.Peer{
|
return &api.Peer{
|
||||||
Id: peer.IP.String(),
|
Id: peer.IP.String(),
|
||||||
Name: peer.Name,
|
Name: peer.Name,
|
||||||
Ip: peer.IP.String(),
|
Ip: peer.IP.String(),
|
||||||
Connected: peer.Status.Connected,
|
Connected: peer.Status.Connected,
|
||||||
LastSeen: peer.Status.LastSeen,
|
LastSeen: peer.Status.LastSeen,
|
||||||
Os: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core),
|
Os: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core),
|
||||||
Version: peer.Meta.WtVersion,
|
Version: peer.Meta.WtVersion,
|
||||||
Groups: groupsInfo,
|
Groups: groupsInfo,
|
||||||
|
SshEnabled: peer.SSHEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -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"
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
s "github.com/netbirdio/netbird/management/server"
|
|
||||||
"github.com/netbirdio/netbird/management/server/http/handler"
|
|
||||||
"github.com/netbirdio/netbird/management/server/http/middleware"
|
|
||||||
"github.com/rs/cors"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/crypto/acme/autocert"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
server *http.Server
|
|
||||||
config *s.HttpServerConfig
|
|
||||||
certManager *autocert.Manager
|
|
||||||
tlsConfig *tls.Config
|
|
||||||
accountManager s.AccountManager
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHttpsServer creates a new HTTPs server (with HTTPS support) and a certManager that is responsible for generating and renewing Let's Encrypt certificate
|
|
||||||
// The listening address will be :443 no matter what was specified in s.HttpServerConfig.Address
|
|
||||||
func NewHttpsServer(
|
|
||||||
config *s.HttpServerConfig,
|
|
||||||
certManager *autocert.Manager,
|
|
||||||
accountManager s.AccountManager,
|
|
||||||
) *Server {
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: config.Address,
|
|
||||||
WriteTimeout: time.Second * 15,
|
|
||||||
ReadTimeout: time.Second * 15,
|
|
||||||
IdleTimeout: time.Second * 60,
|
|
||||||
}
|
|
||||||
return &Server{
|
|
||||||
server: server,
|
|
||||||
config: config,
|
|
||||||
certManager: certManager,
|
|
||||||
accountManager: accountManager,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHttpsServerWithTLSConfig creates a new HTTPs server with a provided tls.Config.
|
|
||||||
// Usually used when you already have a certificate
|
|
||||||
func NewHttpsServerWithTLSConfig(
|
|
||||||
config *s.HttpServerConfig,
|
|
||||||
tlsConfig *tls.Config,
|
|
||||||
accountManager s.AccountManager,
|
|
||||||
) *Server {
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: config.Address,
|
|
||||||
WriteTimeout: time.Second * 15,
|
|
||||||
ReadTimeout: time.Second * 15,
|
|
||||||
IdleTimeout: time.Second * 60,
|
|
||||||
}
|
|
||||||
return &Server{
|
|
||||||
server: server,
|
|
||||||
config: config,
|
|
||||||
tlsConfig: tlsConfig,
|
|
||||||
accountManager: accountManager,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHttpServer creates a new HTTP server (without HTTPS)
|
|
||||||
func NewHttpServer(config *s.HttpServerConfig, accountManager s.AccountManager) *Server {
|
|
||||||
return NewHttpsServer(config, nil, accountManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the http server
|
|
||||||
func (s *Server) Stop(ctx context.Context) error {
|
|
||||||
err := s.server.Shutdown(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start defines http handlers and starts the http server. Blocks until server is shutdown.
|
|
||||||
func (s *Server) Start() error {
|
|
||||||
jwtMiddleware, err := middleware.NewJwtMiddleware(
|
|
||||||
s.config.AuthIssuer,
|
|
||||||
s.config.AuthAudience,
|
|
||||||
s.config.AuthKeysLocation,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
corsMiddleware := cors.AllowAll()
|
|
||||||
|
|
||||||
acMiddleware := middleware.NewAccessControll(
|
|
||||||
s.config.AuthAudience,
|
|
||||||
s.accountManager.IsUserAdmin)
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.Use(jwtMiddleware.Handler, corsMiddleware.Handler, acMiddleware.Handler)
|
|
||||||
|
|
||||||
groupsHandler := handler.NewGroups(s.accountManager, s.config.AuthAudience)
|
|
||||||
rulesHandler := handler.NewRules(s.accountManager, s.config.AuthAudience)
|
|
||||||
peersHandler := handler.NewPeers(s.accountManager, s.config.AuthAudience)
|
|
||||||
keysHandler := handler.NewSetupKeysHandler(s.accountManager, s.config.AuthAudience)
|
|
||||||
userHandler := handler.NewUserHandler(s.accountManager, s.config.AuthAudience)
|
|
||||||
|
|
||||||
r.HandleFunc("/api/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).
|
|
||||||
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/users", userHandler.GetUsers).Methods("GET", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("GET", "POST", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).Methods("GET", "PUT", "OPTIONS")
|
|
||||||
|
|
||||||
r.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("POST", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).
|
|
||||||
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
|
||||||
|
|
||||||
r.HandleFunc("/api/rules", rulesHandler.GetAllRulesHandler).Methods("GET", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/rules", rulesHandler.CreateRuleHandler).Methods("POST", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/rules/{id}", rulesHandler.UpdateRuleHandler).Methods("PUT", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/rules/{id}", rulesHandler.PatchRuleHandler).Methods("PATCH", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/rules/{id}", rulesHandler.GetRuleHandler).Methods("GET", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/rules/{id}", rulesHandler.DeleteRuleHandler).Methods("DELETE", "OPTIONS")
|
|
||||||
|
|
||||||
r.HandleFunc("/api/groups", groupsHandler.GetAllGroupsHandler).Methods("GET", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/groups", groupsHandler.CreateGroupHandler).Methods("POST", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/groups/{id}", groupsHandler.UpdateGroupHandler).Methods("PUT", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/groups/{id}", groupsHandler.PatchGroupHandler).Methods("PATCH", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/groups/{id}", groupsHandler.GetGroupHandler).Methods("GET", "OPTIONS")
|
|
||||||
r.HandleFunc("/api/groups/{id}", groupsHandler.DeleteGroupHandler).Methods("DELETE", "OPTIONS")
|
|
||||||
http.Handle("/", r)
|
|
||||||
|
|
||||||
if s.certManager != nil {
|
|
||||||
// if HTTPS is enabled we reuse the listener from the cert manager
|
|
||||||
listener := s.certManager.Listener()
|
|
||||||
log.Infof(
|
|
||||||
"HTTPs server listening on %s with Let's Encrypt autocert configured",
|
|
||||||
listener.Addr(),
|
|
||||||
)
|
|
||||||
if err = http.Serve(listener, s.certManager.HTTPHandler(r)); err != nil {
|
|
||||||
log.Errorf("failed to serve https server: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if s.tlsConfig != nil {
|
|
||||||
listener, err := tls.Listen("tcp", s.config.Address, s.tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to serve https server: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Infof("HTTPs server listening on %s", listener.Addr())
|
|
||||||
|
|
||||||
if err = http.Serve(listener, r); err != nil {
|
|
||||||
log.Errorf("failed to serve https server: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
log.Infof("HTTP server listening on %s", s.server.Addr)
|
|
||||||
if err = s.server.ListenAndServe(); err != nil {
|
|
||||||
log.Errorf("failed to serve http server: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -359,7 +359,7 @@ func TestServer_GetDeviceAuthorizationFlow(t *testing.T) {
|
|||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
mgmtServer := &Server{
|
mgmtServer := &GRPCServer{
|
||||||
wgKey: testingServerKey,
|
wgKey: testingServerKey,
|
||||||
config: &Config{
|
config: &Config{
|
||||||
DeviceAuthorizationFlow: testCase.inputFlow,
|
DeviceAuthorizationFlow: testCase.inputFlow,
|
||||||
|
|||||||
@@ -422,6 +422,22 @@ var _ = Describe("Management service", func() {
|
|||||||
close(ipChannel)
|
close(ipChannel)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("after login two peers", func() {
|
||||||
|
Specify("then they receive the same network", func() {
|
||||||
|
key, _ := wgtypes.GenerateKey()
|
||||||
|
firstLogin := loginPeerWithValidSetupKey(serverPubKey, key, client)
|
||||||
|
key, _ = wgtypes.GenerateKey()
|
||||||
|
secondLogin := loginPeerWithValidSetupKey(serverPubKey, key, client)
|
||||||
|
|
||||||
|
_, firstLoginNetwork, err := net.ParseCIDR(firstLogin.GetPeerConfig().GetAddress())
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
_, secondLoginNetwork, err := net.ParseCIDR(secondLogin.GetPeerConfig().GetAddress())
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(secondLoginNetwork.String()).To(BeEquivalentTo(firstLoginNetwork.String()))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
func loginPeerWithValidSetupKey(serverPubKey wgtypes.Key, key wgtypes.Key, client mgmtProto.ManagementServiceClient) *mgmtProto.LoginResponse {
|
func loginPeerWithValidSetupKey(serverPubKey wgtypes.Key, key wgtypes.Key, client mgmtProto.ManagementServiceClient) *mgmtProto.LoginResponse {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type MockAccountManager struct {
|
|||||||
DeletePeerFunc func(accountId string, peerKey string) (*server.Peer, error)
|
DeletePeerFunc func(accountId string, peerKey string) (*server.Peer, error)
|
||||||
GetPeerByIPFunc func(accountId string, peerIP string) (*server.Peer, error)
|
GetPeerByIPFunc func(accountId string, peerIP string) (*server.Peer, error)
|
||||||
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
|
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
|
||||||
|
GetPeerNetworkFunc func(peerKey string) (*server.Network, error)
|
||||||
AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, error)
|
AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, error)
|
||||||
GetGroupFunc func(accountID, groupID string) (*server.Group, error)
|
GetGroupFunc func(accountID, groupID string) (*server.Group, error)
|
||||||
SaveGroupFunc func(accountID string, group *server.Group) error
|
SaveGroupFunc func(accountID string, group *server.Group) error
|
||||||
@@ -41,6 +42,8 @@ type MockAccountManager struct {
|
|||||||
ListRulesFunc func(accountID string) ([]*server.Rule, error)
|
ListRulesFunc func(accountID string) ([]*server.Rule, error)
|
||||||
GetUsersFromAccountFunc func(accountID string) ([]*server.UserInfo, error)
|
GetUsersFromAccountFunc func(accountID string) ([]*server.UserInfo, error)
|
||||||
UpdatePeerMetaFunc func(peerKey string, meta server.PeerSystemMeta) error
|
UpdatePeerMetaFunc func(peerKey string, meta server.PeerSystemMeta) error
|
||||||
|
UpdatePeerSSHKeyFunc func(peerKey string, sshKey string) error
|
||||||
|
UpdatePeerFunc func(accountID string, peer *server.Peer) (*server.Peer, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
||||||
@@ -48,7 +51,7 @@ func (am *MockAccountManager) GetUsersFromAccount(accountID string) ([]*server.U
|
|||||||
if am.GetUsersFromAccountFunc != nil {
|
if am.GetUsersFromAccountFunc != nil {
|
||||||
return am.GetUsersFromAccountFunc(accountID)
|
return am.GetUsersFromAccountFunc(accountID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetUsersFromAccount not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetUsersFromAccount is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrCreateAccountByUser mock implementation of GetOrCreateAccountByUser from server.AccountManager interface
|
// GetOrCreateAccountByUser mock implementation of GetOrCreateAccountByUser from server.AccountManager interface
|
||||||
@@ -60,7 +63,7 @@ func (am *MockAccountManager) GetOrCreateAccountByUser(
|
|||||||
}
|
}
|
||||||
return nil, status.Errorf(
|
return nil, status.Errorf(
|
||||||
codes.Unimplemented,
|
codes.Unimplemented,
|
||||||
"method GetOrCreateAccountByUser not implemented",
|
"method GetOrCreateAccountByUser is not implemented",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +72,7 @@ func (am *MockAccountManager) GetAccountByUser(userId string) (*server.Account,
|
|||||||
if am.GetAccountByUserFunc != nil {
|
if am.GetAccountByUserFunc != nil {
|
||||||
return am.GetAccountByUserFunc(userId)
|
return am.GetAccountByUserFunc(userId)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUser not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUser is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSetupKey mock implementation of AddSetupKey from server.AccountManager interface
|
// AddSetupKey mock implementation of AddSetupKey from server.AccountManager interface
|
||||||
@@ -82,7 +85,7 @@ func (am *MockAccountManager) AddSetupKey(
|
|||||||
if am.AddSetupKeyFunc != nil {
|
if am.AddSetupKeyFunc != nil {
|
||||||
return am.AddSetupKeyFunc(accountId, keyName, keyType, expiresIn)
|
return am.AddSetupKeyFunc(accountId, keyName, keyType, expiresIn)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method AddSetupKey not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method AddSetupKey is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevokeSetupKey mock implementation of RevokeSetupKey from server.AccountManager interface
|
// RevokeSetupKey mock implementation of RevokeSetupKey from server.AccountManager interface
|
||||||
@@ -93,7 +96,7 @@ func (am *MockAccountManager) RevokeSetupKey(
|
|||||||
if am.RevokeSetupKeyFunc != nil {
|
if am.RevokeSetupKeyFunc != nil {
|
||||||
return am.RevokeSetupKeyFunc(accountId, keyId)
|
return am.RevokeSetupKeyFunc(accountId, keyId)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RevokeSetupKey not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method RevokeSetupKey is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameSetupKey mock implementation of RenameSetupKey from server.AccountManager interface
|
// RenameSetupKey mock implementation of RenameSetupKey from server.AccountManager interface
|
||||||
@@ -105,7 +108,7 @@ func (am *MockAccountManager) RenameSetupKey(
|
|||||||
if am.RenameSetupKeyFunc != nil {
|
if am.RenameSetupKeyFunc != nil {
|
||||||
return am.RenameSetupKeyFunc(accountId, keyId, newName)
|
return am.RenameSetupKeyFunc(accountId, keyId, newName)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RenameSetupKey not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method RenameSetupKey is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountById mock implementation of GetAccountById from server.AccountManager interface
|
// GetAccountById mock implementation of GetAccountById from server.AccountManager interface
|
||||||
@@ -113,7 +116,7 @@ func (am *MockAccountManager) GetAccountById(accountId string) (*server.Account,
|
|||||||
if am.GetAccountByIdFunc != nil {
|
if am.GetAccountByIdFunc != nil {
|
||||||
return am.GetAccountByIdFunc(accountId)
|
return am.GetAccountByIdFunc(accountId)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountById not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetAccountById is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountByUserOrAccountId mock implementation of GetAccountByUserOrAccountId from server.AccountManager interface
|
// GetAccountByUserOrAccountId mock implementation of GetAccountByUserOrAccountId from server.AccountManager interface
|
||||||
@@ -125,7 +128,7 @@ func (am *MockAccountManager) GetAccountByUserOrAccountId(
|
|||||||
}
|
}
|
||||||
return nil, status.Errorf(
|
return nil, status.Errorf(
|
||||||
codes.Unimplemented,
|
codes.Unimplemented,
|
||||||
"method GetAccountByUserOrAccountId not implemented",
|
"method GetAccountByUserOrAccountId is not implemented",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +141,7 @@ func (am *MockAccountManager) GetAccountWithAuthorizationClaims(
|
|||||||
}
|
}
|
||||||
return nil, status.Errorf(
|
return nil, status.Errorf(
|
||||||
codes.Unimplemented,
|
codes.Unimplemented,
|
||||||
"method GetAccountWithAuthorizationClaims not implemented",
|
"method GetAccountWithAuthorizationClaims is not implemented",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +150,7 @@ func (am *MockAccountManager) AccountExists(accountId string) (*bool, error) {
|
|||||||
if am.AccountExistsFunc != nil {
|
if am.AccountExistsFunc != nil {
|
||||||
return am.AccountExistsFunc(accountId)
|
return am.AccountExistsFunc(accountId)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method AccountExists not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method AccountExists is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeer mock implementation of GetPeer from server.AccountManager interface
|
// GetPeer mock implementation of GetPeer from server.AccountManager interface
|
||||||
@@ -155,7 +158,7 @@ func (am *MockAccountManager) GetPeer(peerKey string) (*server.Peer, error) {
|
|||||||
if am.GetPeerFunc != nil {
|
if am.GetPeerFunc != nil {
|
||||||
return am.GetPeerFunc(peerKey)
|
return am.GetPeerFunc(peerKey)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeer not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetPeer is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkPeerConnected mock implementation of MarkPeerConnected from server.AccountManager interface
|
// MarkPeerConnected mock implementation of MarkPeerConnected from server.AccountManager interface
|
||||||
@@ -163,7 +166,7 @@ func (am *MockAccountManager) MarkPeerConnected(peerKey string, connected bool)
|
|||||||
if am.MarkPeerConnectedFunc != nil {
|
if am.MarkPeerConnectedFunc != nil {
|
||||||
return am.MarkPeerConnectedFunc(peerKey, connected)
|
return am.MarkPeerConnectedFunc(peerKey, connected)
|
||||||
}
|
}
|
||||||
return status.Errorf(codes.Unimplemented, "method MarkPeerConnected not implemented")
|
return status.Errorf(codes.Unimplemented, "method MarkPeerConnected is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenamePeer mock implementation of RenamePeer from server.AccountManager interface
|
// RenamePeer mock implementation of RenamePeer from server.AccountManager interface
|
||||||
@@ -175,7 +178,7 @@ func (am *MockAccountManager) RenamePeer(
|
|||||||
if am.RenamePeerFunc != nil {
|
if am.RenamePeerFunc != nil {
|
||||||
return am.RenamePeerFunc(accountId, peerKey, newName)
|
return am.RenamePeerFunc(accountId, peerKey, newName)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RenamePeer not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method RenamePeer is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePeer mock implementation of DeletePeer from server.AccountManager interface
|
// DeletePeer mock implementation of DeletePeer from server.AccountManager interface
|
||||||
@@ -183,7 +186,7 @@ func (am *MockAccountManager) DeletePeer(accountId string, peerKey string) (*ser
|
|||||||
if am.DeletePeerFunc != nil {
|
if am.DeletePeerFunc != nil {
|
||||||
return am.DeletePeerFunc(accountId, peerKey)
|
return am.DeletePeerFunc(accountId, peerKey)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeletePeer not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method DeletePeer is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerByIP mock implementation of GetPeerByIP from server.AccountManager interface
|
// GetPeerByIP mock implementation of GetPeerByIP from server.AccountManager interface
|
||||||
@@ -191,7 +194,7 @@ func (am *MockAccountManager) GetPeerByIP(accountId string, peerIP string) (*ser
|
|||||||
if am.GetPeerByIPFunc != nil {
|
if am.GetPeerByIPFunc != nil {
|
||||||
return am.GetPeerByIPFunc(accountId, peerIP)
|
return am.GetPeerByIPFunc(accountId, peerIP)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeerByIP not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetPeerByIP is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNetworkMap mock implementation of GetNetworkMap from server.AccountManager interface
|
// GetNetworkMap mock implementation of GetNetworkMap from server.AccountManager interface
|
||||||
@@ -199,7 +202,15 @@ func (am *MockAccountManager) GetNetworkMap(peerKey string) (*server.NetworkMap,
|
|||||||
if am.GetNetworkMapFunc != nil {
|
if am.GetNetworkMapFunc != nil {
|
||||||
return am.GetNetworkMapFunc(peerKey)
|
return am.GetNetworkMapFunc(peerKey)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetNetworkMap not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetNetworkMap is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeerNetwork mock implementation of GetPeerNetwork from server.AccountManager interface
|
||||||
|
func (am *MockAccountManager) GetPeerNetwork(peerKey string) (*server.Network, error) {
|
||||||
|
if am.GetPeerNetworkFunc != nil {
|
||||||
|
return am.GetPeerNetworkFunc(peerKey)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetPeerNetwork is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPeer mock implementation of AddPeer from server.AccountManager interface
|
// AddPeer mock implementation of AddPeer from server.AccountManager interface
|
||||||
@@ -211,7 +222,7 @@ func (am *MockAccountManager) AddPeer(
|
|||||||
if am.AddPeerFunc != nil {
|
if am.AddPeerFunc != nil {
|
||||||
return am.AddPeerFunc(setupKey, userId, peer)
|
return am.AddPeerFunc(setupKey, userId, peer)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method AddPeer not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method AddPeer is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGroup mock implementation of GetGroup from server.AccountManager interface
|
// GetGroup mock implementation of GetGroup from server.AccountManager interface
|
||||||
@@ -219,7 +230,7 @@ func (am *MockAccountManager) GetGroup(accountID, groupID string) (*server.Group
|
|||||||
if am.GetGroupFunc != nil {
|
if am.GetGroupFunc != nil {
|
||||||
return am.GetGroupFunc(accountID, groupID)
|
return am.GetGroupFunc(accountID, groupID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetGroup not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetGroup is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveGroup mock implementation of SaveGroup from server.AccountManager interface
|
// SaveGroup mock implementation of SaveGroup from server.AccountManager interface
|
||||||
@@ -227,7 +238,7 @@ func (am *MockAccountManager) SaveGroup(accountID string, group *server.Group) e
|
|||||||
if am.SaveGroupFunc != nil {
|
if am.SaveGroupFunc != nil {
|
||||||
return am.SaveGroupFunc(accountID, group)
|
return am.SaveGroupFunc(accountID, group)
|
||||||
}
|
}
|
||||||
return status.Errorf(codes.Unimplemented, "method SaveGroup not implemented")
|
return status.Errorf(codes.Unimplemented, "method SaveGroup is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateGroup mock implementation of UpdateGroup from server.AccountManager interface
|
// UpdateGroup mock implementation of UpdateGroup from server.AccountManager interface
|
||||||
@@ -243,7 +254,7 @@ func (am *MockAccountManager) DeleteGroup(accountID, groupID string) error {
|
|||||||
if am.DeleteGroupFunc != nil {
|
if am.DeleteGroupFunc != nil {
|
||||||
return am.DeleteGroupFunc(accountID, groupID)
|
return am.DeleteGroupFunc(accountID, groupID)
|
||||||
}
|
}
|
||||||
return status.Errorf(codes.Unimplemented, "method DeleteGroup not implemented")
|
return status.Errorf(codes.Unimplemented, "method DeleteGroup is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListGroups mock implementation of ListGroups from server.AccountManager interface
|
// ListGroups mock implementation of ListGroups from server.AccountManager interface
|
||||||
@@ -251,7 +262,7 @@ func (am *MockAccountManager) ListGroups(accountID string) ([]*server.Group, err
|
|||||||
if am.ListGroupsFunc != nil {
|
if am.ListGroupsFunc != nil {
|
||||||
return am.ListGroupsFunc(accountID)
|
return am.ListGroupsFunc(accountID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListGroups not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ListGroups is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupAddPeer mock implementation of GroupAddPeer from server.AccountManager interface
|
// GroupAddPeer mock implementation of GroupAddPeer from server.AccountManager interface
|
||||||
@@ -259,7 +270,7 @@ func (am *MockAccountManager) GroupAddPeer(accountID, groupID, peerKey string) e
|
|||||||
if am.GroupAddPeerFunc != nil {
|
if am.GroupAddPeerFunc != nil {
|
||||||
return am.GroupAddPeerFunc(accountID, groupID, peerKey)
|
return am.GroupAddPeerFunc(accountID, groupID, peerKey)
|
||||||
}
|
}
|
||||||
return status.Errorf(codes.Unimplemented, "method GroupAddPeer not implemented")
|
return status.Errorf(codes.Unimplemented, "method GroupAddPeer is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupDeletePeer mock implementation of GroupDeletePeer from server.AccountManager interface
|
// GroupDeletePeer mock implementation of GroupDeletePeer from server.AccountManager interface
|
||||||
@@ -267,7 +278,7 @@ func (am *MockAccountManager) GroupDeletePeer(accountID, groupID, peerKey string
|
|||||||
if am.GroupDeletePeerFunc != nil {
|
if am.GroupDeletePeerFunc != nil {
|
||||||
return am.GroupDeletePeerFunc(accountID, groupID, peerKey)
|
return am.GroupDeletePeerFunc(accountID, groupID, peerKey)
|
||||||
}
|
}
|
||||||
return status.Errorf(codes.Unimplemented, "method GroupDeletePeer not implemented")
|
return status.Errorf(codes.Unimplemented, "method GroupDeletePeer is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupListPeers mock implementation of GroupListPeers from server.AccountManager interface
|
// GroupListPeers mock implementation of GroupListPeers from server.AccountManager interface
|
||||||
@@ -275,7 +286,7 @@ func (am *MockAccountManager) GroupListPeers(accountID, groupID string) ([]*serv
|
|||||||
if am.GroupListPeersFunc != nil {
|
if am.GroupListPeersFunc != nil {
|
||||||
return am.GroupListPeersFunc(accountID, groupID)
|
return am.GroupListPeersFunc(accountID, groupID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GroupListPeers not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GroupListPeers is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRule mock implementation of GetRule from server.AccountManager interface
|
// GetRule mock implementation of GetRule from server.AccountManager interface
|
||||||
@@ -283,7 +294,7 @@ func (am *MockAccountManager) GetRule(accountID, ruleID string) (*server.Rule, e
|
|||||||
if am.GetRuleFunc != nil {
|
if am.GetRuleFunc != nil {
|
||||||
return am.GetRuleFunc(accountID, ruleID)
|
return am.GetRuleFunc(accountID, ruleID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetRule not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetRule is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveRule mock implementation of SaveRule from server.AccountManager interface
|
// SaveRule mock implementation of SaveRule from server.AccountManager interface
|
||||||
@@ -291,7 +302,7 @@ func (am *MockAccountManager) SaveRule(accountID string, rule *server.Rule) erro
|
|||||||
if am.SaveRuleFunc != nil {
|
if am.SaveRuleFunc != nil {
|
||||||
return am.SaveRuleFunc(accountID, rule)
|
return am.SaveRuleFunc(accountID, rule)
|
||||||
}
|
}
|
||||||
return status.Errorf(codes.Unimplemented, "method SaveRule not implemented")
|
return status.Errorf(codes.Unimplemented, "method SaveRule is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRule mock implementation of UpdateRule from server.AccountManager interface
|
// UpdateRule mock implementation of UpdateRule from server.AccountManager interface
|
||||||
@@ -307,7 +318,7 @@ func (am *MockAccountManager) DeleteRule(accountID, ruleID string) error {
|
|||||||
if am.DeleteRuleFunc != nil {
|
if am.DeleteRuleFunc != nil {
|
||||||
return am.DeleteRuleFunc(accountID, ruleID)
|
return am.DeleteRuleFunc(accountID, ruleID)
|
||||||
}
|
}
|
||||||
return status.Errorf(codes.Unimplemented, "method DeleteRule not implemented")
|
return status.Errorf(codes.Unimplemented, "method DeleteRule is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRules mock implementation of ListRules from server.AccountManager interface
|
// ListRules mock implementation of ListRules from server.AccountManager interface
|
||||||
@@ -315,7 +326,7 @@ func (am *MockAccountManager) ListRules(accountID string) ([]*server.Rule, error
|
|||||||
if am.ListRulesFunc != nil {
|
if am.ListRulesFunc != nil {
|
||||||
return am.ListRulesFunc(accountID)
|
return am.ListRulesFunc(accountID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListRules not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ListRules is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePeerMeta mock implementation of UpdatePeerMeta from server.AccountManager interface
|
// UpdatePeerMeta mock implementation of UpdatePeerMeta from server.AccountManager interface
|
||||||
@@ -323,7 +334,7 @@ func (am *MockAccountManager) UpdatePeerMeta(peerKey string, meta server.PeerSys
|
|||||||
if am.UpdatePeerMetaFunc != nil {
|
if am.UpdatePeerMetaFunc != nil {
|
||||||
return am.UpdatePeerMetaFunc(peerKey, meta)
|
return am.UpdatePeerMetaFunc(peerKey, meta)
|
||||||
}
|
}
|
||||||
return status.Errorf(codes.Unimplemented, "method UpdatePeerMetaFunc not implemented")
|
return status.Errorf(codes.Unimplemented, "method UpdatePeerMetaFunc is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUserAdmin mock implementation of IsUserAdmin from server.AccountManager interface
|
// IsUserAdmin mock implementation of IsUserAdmin from server.AccountManager interface
|
||||||
@@ -331,5 +342,21 @@ func (am *MockAccountManager) IsUserAdmin(claims jwtclaims.AuthorizationClaims)
|
|||||||
if am.IsUserAdminFunc != nil {
|
if am.IsUserAdminFunc != nil {
|
||||||
return am.IsUserAdminFunc(claims)
|
return am.IsUserAdminFunc(claims)
|
||||||
}
|
}
|
||||||
return false, status.Errorf(codes.Unimplemented, "method IsUserAdmin not implemented")
|
return false, status.Errorf(codes.Unimplemented, "method IsUserAdmin is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePeerSSHKey mocks UpdatePeerSSHKey function of the account manager
|
||||||
|
func (am *MockAccountManager) UpdatePeerSSHKey(peerKey string, sshKey string) error {
|
||||||
|
if am.UpdatePeerSSHKeyFunc != nil {
|
||||||
|
return am.UpdatePeerSSHKeyFunc(peerKey, sshKey)
|
||||||
|
}
|
||||||
|
return status.Errorf(codes.Unimplemented, "method UpdatePeerSSHKey is is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePeer mocks UpdatePeerFunc function of the account manager
|
||||||
|
func (am *MockAccountManager) UpdatePeer(accountID string, peer *server.Peer) (*server.Peer, error) {
|
||||||
|
if am.UpdatePeerFunc != nil {
|
||||||
|
return am.UpdatePeerFunc(accountID, peer)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method UpdatePeerFunc is is not implemented")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,18 +47,24 @@ type Peer struct {
|
|||||||
Status *PeerStatus
|
Status *PeerStatus
|
||||||
// The user ID that registered the peer
|
// The user ID that registered the peer
|
||||||
UserID string
|
UserID string
|
||||||
|
// SSHKey is a public SSH key of the peer
|
||||||
|
SSHKey string
|
||||||
|
// SSHEnabled indicated whether SSH server is enabled on the peer
|
||||||
|
SSHEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy copies Peer object
|
// Copy copies Peer object
|
||||||
func (p *Peer) Copy() *Peer {
|
func (p *Peer) Copy() *Peer {
|
||||||
return &Peer{
|
return &Peer{
|
||||||
Key: p.Key,
|
Key: p.Key,
|
||||||
SetupKey: p.SetupKey,
|
SetupKey: p.SetupKey,
|
||||||
IP: p.IP,
|
IP: p.IP,
|
||||||
Meta: p.Meta,
|
Meta: p.Meta,
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Status: p.Status,
|
Status: p.Status,
|
||||||
UserID: p.UserID,
|
UserID: p.UserID,
|
||||||
|
SSHKey: p.SSHKey,
|
||||||
|
SSHEnabled: p.SSHEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +106,41 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerKey string, connected boo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdatePeer updates peer. Only Peer.Name and Peer.SSHEnabled can be updated.
|
||||||
|
func (am *DefaultAccountManager) UpdatePeer(accountID string, update *Peer) (*Peer, 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, err := am.Store.GetPeer(update.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerCopy := peer.Copy()
|
||||||
|
if peer.Name != "" {
|
||||||
|
peerCopy.Name = update.Name
|
||||||
|
}
|
||||||
|
peerCopy.SSHEnabled = update.SSHEnabled
|
||||||
|
|
||||||
|
err = am.Store.SavePeer(accountID, peerCopy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.updateAccountPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return peerCopy, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// RenamePeer changes peer's name
|
// RenamePeer changes peer's name
|
||||||
func (am *DefaultAccountManager) RenamePeer(
|
func (am *DefaultAccountManager) RenamePeer(
|
||||||
accountId string,
|
accountId string,
|
||||||
@@ -216,6 +257,19 @@ func (am *DefaultAccountManager) GetNetworkMap(peerKey string) (*NetworkMap, err
|
|||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPeerNetwork returns the Network for a given peer
|
||||||
|
func (am *DefaultAccountManager) GetPeerNetwork(peerKey string) (*Network, error) {
|
||||||
|
am.mux.Lock()
|
||||||
|
defer am.mux.Unlock()
|
||||||
|
|
||||||
|
account, err := am.Store.GetPeerAccount(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "Invalid peer key %s", peerKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.Network.Copy(), err
|
||||||
|
}
|
||||||
|
|
||||||
// AddPeer adds a new peer to the Store.
|
// AddPeer adds a new peer to the Store.
|
||||||
// Each Account has a list of pre-authorised SetupKey and if no Account has a given key err wit ha code codes.Unauthenticated
|
// Each Account has a list of pre-authorised SetupKey and if no Account has a given key err wit ha code codes.Unauthenticated
|
||||||
// will be returned, meaning the key is invalid
|
// will be returned, meaning the key is invalid
|
||||||
@@ -285,13 +339,15 @@ func (am *DefaultAccountManager) AddPeer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
newPeer := &Peer{
|
newPeer := &Peer{
|
||||||
Key: peer.Key,
|
Key: peer.Key,
|
||||||
SetupKey: upperKey,
|
SetupKey: upperKey,
|
||||||
IP: nextIp,
|
IP: nextIp,
|
||||||
Meta: peer.Meta,
|
Meta: peer.Meta,
|
||||||
Name: peer.Name,
|
Name: peer.Name,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Status: &PeerStatus{Connected: false, LastSeen: time.Now()},
|
Status: &PeerStatus{Connected: false, LastSeen: time.Now()},
|
||||||
|
SSHEnabled: false,
|
||||||
|
SSHKey: peer.SSHKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
// add peer to 'All' group
|
// add peer to 'All' group
|
||||||
@@ -315,6 +371,38 @@ func (am *DefaultAccountManager) AddPeer(
|
|||||||
return newPeer, nil
|
return newPeer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdatePeerSSHKey updates peer's public SSH key
|
||||||
|
func (am *DefaultAccountManager) UpdatePeerSSHKey(peerKey string, sshKey string) error {
|
||||||
|
am.mux.Lock()
|
||||||
|
defer am.mux.Unlock()
|
||||||
|
|
||||||
|
if sshKey == "" {
|
||||||
|
log.Debugf("empty SSH key provided for peer %s, skipping update", peerKey)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, err := am.Store.GetPeer(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := am.Store.GetPeerAccount(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerCopy := peer.Copy()
|
||||||
|
peerCopy.SSHKey = sshKey
|
||||||
|
|
||||||
|
err = am.Store.SavePeer(account.Id, peerCopy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// trigger network map update
|
||||||
|
return am.updateAccountPeers(account)
|
||||||
|
}
|
||||||
|
|
||||||
// UpdatePeerMeta updates peer's system metadata
|
// UpdatePeerMeta updates peer's system metadata
|
||||||
func (am *DefaultAccountManager) UpdatePeerMeta(peerKey string, meta PeerSystemMeta) error {
|
func (am *DefaultAccountManager) UpdatePeerMeta(peerKey string, meta PeerSystemMeta) error {
|
||||||
am.mux.Lock()
|
am.mux.Lock()
|
||||||
@@ -345,7 +433,7 @@ func (am *DefaultAccountManager) UpdatePeerMeta(peerKey string, meta PeerSystemM
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPeersByACL allowed for given peer by ACL
|
// getPeersByACL returns all peers that given peer has access to.
|
||||||
func (am *DefaultAccountManager) getPeersByACL(account *Account, peerKey string) []*Peer {
|
func (am *DefaultAccountManager) getPeersByACL(account *Account, peerKey string) []*Peer {
|
||||||
var peers []*Peer
|
var peers []*Peer
|
||||||
srcRules, err := am.Store.GetPeerSrcRules(account.Id, peerKey)
|
srcRules, err := am.Store.GetPeerSrcRules(account.Id, peerKey)
|
||||||
@@ -409,7 +497,8 @@ func (am *DefaultAccountManager) getPeersByACL(account *Account, peerKey string)
|
|||||||
return peers
|
return peers
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateAccountPeers network map constructed by ACL
|
// updateAccountPeers updates all peers that belong to an account.
|
||||||
|
// Should be called when changes have to be synced to peers.
|
||||||
func (am *DefaultAccountManager) updateAccountPeers(account *Account) error {
|
func (am *DefaultAccountManager) updateAccountPeers(account *Account) error {
|
||||||
// notify other peers of the change
|
// notify other peers of the change
|
||||||
peers, err := am.Store.GetAccountPeers(account.Id)
|
peers, err := am.Store.GetAccountPeers(account.Id)
|
||||||
@@ -417,12 +506,14 @@ func (am *DefaultAccountManager) updateAccountPeers(account *Account) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
network := account.Network.Copy()
|
||||||
|
|
||||||
for _, p := range peers {
|
for _, p := range peers {
|
||||||
update := toRemotePeerConfig(am.getPeersByACL(account, p.Key))
|
update := toRemotePeerConfig(am.getPeersByACL(account, p.Key))
|
||||||
err = am.peersUpdateManager.SendUpdate(p.Key,
|
err = am.peersUpdateManager.SendUpdate(p.Key,
|
||||||
&UpdateMessage{
|
&UpdateMessage{
|
||||||
Update: &proto.SyncResponse{
|
Update: &proto.SyncResponse{
|
||||||
// fill those field for backward compatibility
|
// fill deprecated fields for backward compatibility
|
||||||
RemotePeers: update,
|
RemotePeers: update,
|
||||||
RemotePeersIsEmpty: len(update) == 0,
|
RemotePeersIsEmpty: len(update) == 0,
|
||||||
// new field
|
// new field
|
||||||
@@ -430,6 +521,7 @@ func (am *DefaultAccountManager) updateAccountPeers(account *Account) error {
|
|||||||
Serial: account.Network.CurrentSerial(),
|
Serial: account.Network.CurrentSerial(),
|
||||||
RemotePeers: update,
|
RemotePeers: update,
|
||||||
RemotePeersIsEmpty: len(update) == 0,
|
RemotePeersIsEmpty: len(update) == 0,
|
||||||
|
PeerConfig: toPeerConfig(p, network),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -253,3 +253,69 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
|
|||||||
t.Errorf("expecting Account NetworkMap to have 0 peers, got %v", len(networkMap2.Peers))
|
t.Errorf("expecting Account NetworkMap to have 0 peers, got %v", len(networkMap2.Peers))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccountManager_GetPeerNetwork(t *testing.T) {
|
||||||
|
manager, err := createManager(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedId := "test_account"
|
||||||
|
userId := "account_creator"
|
||||||
|
account, err := createAccount(manager, expectedId, userId, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var setupKey *SetupKey
|
||||||
|
for _, key := range account.SetupKeys {
|
||||||
|
if key.Type == SetupKeyReusable {
|
||||||
|
setupKey = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKey1, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = manager.AddPeer(setupKey.Key, "", &Peer{
|
||||||
|
Key: peerKey1.PublicKey().String(),
|
||||||
|
Meta: PeerSystemMeta{},
|
||||||
|
Name: "test-peer-2",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expecting peer to be added, got failure %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKey2, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = manager.AddPeer(setupKey.Key, "", &Peer{
|
||||||
|
Key: peerKey2.PublicKey().String(),
|
||||||
|
Meta: PeerSystemMeta{},
|
||||||
|
Name: "test-peer-2",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expecting peer to be added, got failure %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
network, err := manager.GetPeerNetwork(peerKey1.PublicKey().String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.Network.Id != network.Id {
|
||||||
|
t.Errorf("expecting Account Networks ID to be equal, got %s expected %s", network.Id, account.Network.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
type UpdateMessage struct {
|
type UpdateMessage struct {
|
||||||
Update *proto.SyncResponse
|
Update *proto.SyncResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
type PeersUpdateManager struct {
|
type PeersUpdateManager struct {
|
||||||
peerChannels map[string]chan *UpdateMessage
|
peerChannels map[string]chan *UpdateMessage
|
||||||
channelsMux *sync.Mutex
|
channelsMux *sync.Mutex
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
|
|||||||
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
sigCtx, cancel := context.WithTimeout(ctx, time.Second*3)
|
sigCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
conn, err := grpc.DialContext(
|
conn, err := grpc.DialContext(
|
||||||
sigCtx,
|
sigCtx,
|
||||||
@@ -75,6 +75,8 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("connected to Signal Service: %v", conn.Target())
|
||||||
|
|
||||||
return &GrpcClient{
|
return &GrpcClient{
|
||||||
realClient: proto.NewSignalExchangeClient(conn),
|
realClient: proto.NewSignalExchangeClient(conn),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -89,14 +91,13 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
|
|||||||
func defaultBackoff(ctx context.Context) backoff.BackOff {
|
func defaultBackoff(ctx context.Context) backoff.BackOff {
|
||||||
return backoff.WithContext(&backoff.ExponentialBackOff{
|
return backoff.WithContext(&backoff.ExponentialBackOff{
|
||||||
InitialInterval: 800 * time.Millisecond,
|
InitialInterval: 800 * time.Millisecond,
|
||||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
RandomizationFactor: 1,
|
||||||
Multiplier: backoff.DefaultMultiplier,
|
Multiplier: 1.7,
|
||||||
MaxInterval: 10 * time.Second,
|
MaxInterval: 10 * time.Second,
|
||||||
MaxElapsedTime: 12 * time.Hour, //stop after 12 hours of trying, the error will be propagated to the general retry of the client
|
MaxElapsedTime: 3 * 30 * 24 * time.Hour, // 3 months
|
||||||
Stop: backoff.Stop,
|
Stop: backoff.Stop,
|
||||||
Clock: backoff.SystemClock,
|
Clock: backoff.SystemClock,
|
||||||
}, ctx)
|
}, ctx)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive Connects to the Signal Exchange message stream and starts receiving messages.
|
// Receive Connects to the Signal Exchange message stream and starts receiving messages.
|
||||||
@@ -112,8 +113,12 @@ func (c *GrpcClient) Receive(msgHandler func(msg *proto.Message) error) error {
|
|||||||
c.notifyStreamDisconnected()
|
c.notifyStreamDisconnected()
|
||||||
|
|
||||||
log.Debugf("signal connection state %v", c.signalConn.GetState())
|
log.Debugf("signal connection state %v", c.signalConn.GetState())
|
||||||
if !c.Ready() {
|
connState := c.signalConn.GetState()
|
||||||
return fmt.Errorf("no connection to signal")
|
if connState == connectivity.Shutdown {
|
||||||
|
return backoff.Permanent(fmt.Errorf("connection to signal has been shut down"))
|
||||||
|
} else if !(connState == connectivity.Ready || connState == connectivity.Idle) {
|
||||||
|
c.signalConn.WaitForStateChange(c.ctx, connState)
|
||||||
|
return fmt.Errorf("connection to signal is not ready and in %s state", connState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// connect to Signal stream identifying ourselves with a public Wireguard key
|
// connect to Signal stream identifying ourselves with a public Wireguard key
|
||||||
@@ -131,7 +136,8 @@ func (c *GrpcClient) Receive(msgHandler func(msg *proto.Message) error) error {
|
|||||||
// start receiving messages from the Signal stream (from other peers through signal)
|
// start receiving messages from the Signal stream (from other peers through signal)
|
||||||
err = c.receive(stream, msgHandler)
|
err = c.receive(stream, msgHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("disconnected from the Signal Exchange due to an error: %v", err)
|
// we need this reset because after a successful connection and a consequent error, backoff lib doesn't
|
||||||
|
// reset times and next try will start with a long delay
|
||||||
backOff.Reset()
|
backOff.Reset()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -141,7 +147,7 @@ func (c *GrpcClient) Receive(msgHandler func(msg *proto.Message) error) error {
|
|||||||
|
|
||||||
err := backoff.Retry(operation, backOff)
|
err := backoff.Retry(operation, backOff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("exiting Signal Service connection retry loop due to unrecoverable error: %s", err)
|
log.Errorf("exiting the Signal service connection retry loop due to the unrecoverable error: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,13 +314,13 @@ func (c *GrpcClient) receive(stream proto.SignalExchange_ConnectStreamClient,
|
|||||||
for {
|
for {
|
||||||
msg, err := stream.Recv()
|
msg, err := stream.Recv()
|
||||||
if s, ok := status.FromError(err); ok && s.Code() == codes.Canceled {
|
if s, ok := status.FromError(err); ok && s.Code() == codes.Canceled {
|
||||||
log.Warnf("stream canceled (usually indicates shutdown)")
|
log.Debugf("stream canceled (usually indicates shutdown)")
|
||||||
return err
|
return err
|
||||||
} else if s.Code() == codes.Unavailable {
|
} else if s.Code() == codes.Unavailable {
|
||||||
log.Warnf("Signal Service is unavailable")
|
log.Debugf("Signal Service is unavailable")
|
||||||
return err
|
return err
|
||||||
} else if err == io.EOF {
|
} else if err == io.EOF {
|
||||||
log.Warnf("Signal Service stream closed by server")
|
log.Debugf("Signal Service stream closed by server")
|
||||||
return err
|
return err
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
@@ -29,6 +31,7 @@ var (
|
|||||||
signalLetsencryptDomain string
|
signalLetsencryptDomain string
|
||||||
signalSSLDir string
|
signalSSLDir string
|
||||||
defaultSignalSSLDir string
|
defaultSignalSSLDir string
|
||||||
|
tlsEnabled bool
|
||||||
|
|
||||||
signalKaep = grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
|
signalKaep = grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
|
||||||
MinTime: 5 * time.Second,
|
MinTime: 5 * time.Second,
|
||||||
@@ -44,9 +47,26 @@ var (
|
|||||||
|
|
||||||
runCmd = &cobra.Command{
|
runCmd = &cobra.Command{
|
||||||
Use: "run",
|
Use: "run",
|
||||||
Short: "start Netbird Signal Server daemon",
|
Short: "start NetBird Signal Server daemon",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
// detect whether user specified a port
|
||||||
|
userPort := cmd.Flag("port").Changed
|
||||||
|
if signalLetsencryptDomain != "" {
|
||||||
|
tlsEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !userPort {
|
||||||
|
// different defaults for signalPort
|
||||||
|
if tlsEnabled {
|
||||||
|
signalPort = 443
|
||||||
|
} else {
|
||||||
|
signalPort = 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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)
|
log.Fatalf("failed initializing log %v", err)
|
||||||
@@ -62,47 +82,120 @@ var (
|
|||||||
}
|
}
|
||||||
|
|
||||||
var opts []grpc.ServerOption
|
var opts []grpc.ServerOption
|
||||||
if signalLetsencryptDomain != "" {
|
var certManager *autocert.Manager
|
||||||
if _, err := os.Stat(signalSSLDir); os.IsNotExist(err) {
|
if tlsEnabled {
|
||||||
err = os.MkdirAll(signalSSLDir, os.ModeDir)
|
// Let's encrypt enabled -> generate certificate automatically
|
||||||
if err != nil {
|
certManager, err = encryption.CreateCertManager(signalSSLDir, signalLetsencryptDomain)
|
||||||
log.Fatalf("failed creating datadir: %s: %v", signalSSLDir, err)
|
if err != nil {
|
||||||
}
|
return err
|
||||||
}
|
}
|
||||||
certManager := encryption.CreateCertManager(signalSSLDir, signalLetsencryptDomain)
|
|
||||||
transportCredentials := credentials.NewTLS(certManager.TLSConfig())
|
transportCredentials := credentials.NewTLS(certManager.TLSConfig())
|
||||||
opts = append(opts, grpc.Creds(transportCredentials))
|
opts = append(opts, grpc.Creds(transportCredentials))
|
||||||
|
|
||||||
listener := certManager.Listener()
|
|
||||||
log.Infof("http server listening on %s", listener.Addr())
|
|
||||||
go func() {
|
|
||||||
if err := http.Serve(listener, certManager.HTTPHandler(nil)); err != nil {
|
|
||||||
log.Errorf("failed to serve https server: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opts = append(opts, signalKaep, signalKasp)
|
opts = append(opts, signalKaep, signalKasp)
|
||||||
grpcServer := grpc.NewServer(opts...)
|
grpcServer := grpc.NewServer(opts...)
|
||||||
|
|
||||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", signalPort))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to listen: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proto.RegisterSignalExchangeServer(grpcServer, server.NewServer())
|
proto.RegisterSignalExchangeServer(grpcServer, server.NewServer())
|
||||||
log.Printf("started server: localhost:%v", signalPort)
|
|
||||||
if err := grpcServer.Serve(lis); err != nil {
|
var compatListener net.Listener
|
||||||
log.Fatalf("failed to serve: %v", err)
|
if signalPort != 10000 {
|
||||||
|
// The Signal gRPC server was running on port 10000 previously. Old agents that are already connected to Signal
|
||||||
|
// are using port 10000. For compatibility purposes we keep running a 2nd gRPC server on port 10000.
|
||||||
|
compatListener, err = serveGRPC(grpcServer, 10000)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("running gRPC backward compatibility server: %s", compatListener.Addr().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var grpcListener net.Listener
|
||||||
|
var httpListener net.Listener
|
||||||
|
if tlsEnabled {
|
||||||
|
httpListener = certManager.Listener()
|
||||||
|
if signalPort == 443 {
|
||||||
|
// running gRPC and HTTP cert manager on the same port
|
||||||
|
serveHTTP(httpListener, certManager.HTTPHandler(grpcHandlerFunc(grpcServer)))
|
||||||
|
log.Infof("running HTTP server (LetsEncrypt challenge handler) and gRPC server on the same port: %s", httpListener.Addr().String())
|
||||||
|
} else {
|
||||||
|
serveHTTP(httpListener, certManager.HTTPHandler(nil))
|
||||||
|
log.Infof("running HTTP server (LetsEncrypt challenge handler): %s", httpListener.Addr().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if signalPort != 443 || !tlsEnabled {
|
||||||
|
grpcListener, err = serveGRPC(grpcServer, signalPort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("running gRPC server: %s", grpcListener.Addr().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("started Signal Service")
|
||||||
|
|
||||||
SetupCloseHandler()
|
SetupCloseHandler()
|
||||||
|
|
||||||
<-stopCh
|
<-stopCh
|
||||||
log.Println("Receive signal to stop running the Signal server")
|
if grpcListener != nil {
|
||||||
|
_ = grpcListener.Close()
|
||||||
|
log.Infof("stopped gRPC server")
|
||||||
|
}
|
||||||
|
if httpListener != nil {
|
||||||
|
_ = httpListener.Close()
|
||||||
|
log.Infof("stopped HTTP server")
|
||||||
|
}
|
||||||
|
if compatListener != nil {
|
||||||
|
_ = compatListener.Close()
|
||||||
|
log.Infof("stopped gRPC backward compatibility server")
|
||||||
|
}
|
||||||
|
log.Infof("stopped Signal Service")
|
||||||
|
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func grpcHandlerFunc(grpcServer *grpc.Server) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
grpcHeader := strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") ||
|
||||||
|
strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc+proto")
|
||||||
|
if r.ProtoMajor == 2 && grpcHeader {
|
||||||
|
grpcServer.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func notifyStop(msg string) {
|
||||||
|
select {
|
||||||
|
case stopCh <- 1:
|
||||||
|
log.Error(msg)
|
||||||
|
default:
|
||||||
|
// stop has been already called, nothing to report
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 cpFile(src, dst string) error {
|
func cpFile(src, dst string) error {
|
||||||
var err error
|
var err error
|
||||||
var srcfd *os.File
|
var srcfd *os.File
|
||||||
@@ -191,7 +284,7 @@ func migrateToNetbird(oldPath, newPath string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
runCmd.PersistentFlags().IntVar(&signalPort, "port", 10000, "Server port to listen on (e.g. 10000)")
|
runCmd.PersistentFlags().IntVar(&signalPort, "port", 80, "Server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise")
|
||||||
runCmd.Flags().StringVar(&signalSSLDir, "ssl-dir", defaultSignalSSLDir, "server ssl directory location. *Required only for Let's Encrypt certificates.")
|
runCmd.Flags().StringVar(&signalSSLDir, "ssl-dir", defaultSignalSSLDir, "server ssl directory location. *Required only for Let's Encrypt certificates.")
|
||||||
runCmd.Flags().StringVar(&signalLetsencryptDomain, "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")
|
runCmd.Flags().StringVar(&signalLetsencryptDomain, "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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
// SliceDiff returns the elements in slice `x` that are not in slice `y`
|
// SliceDiff returns the elements in slice `x` that are not in slice `y`
|
||||||
func SliceDiff(x, y []string) []string {
|
func SliceDiff(x, y []string) []string {
|
||||||
mapY := make(map[string]struct{}, len(y))
|
mapY := make(map[string]struct{}, len(y))
|
||||||
@@ -14,3 +16,9 @@ func SliceDiff(x, y []string) []string {
|
|||||||
}
|
}
|
||||||
return diff
|
return diff
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FileExists returns true if specified file exists
|
||||||
|
func FileExists(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|||||||
12
util/membership_unix.go
Normal file
12
util/membership_unix.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsAdmin returns true if user is root
|
||||||
|
func IsAdmin() bool {
|
||||||
|
return os.Geteuid() == 0
|
||||||
|
}
|
||||||
12
util/membership_windows.go
Normal file
12
util/membership_windows.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "golang.zx2c4.com/wireguard/windows/elevate"
|
||||||
|
|
||||||
|
// IsAdmin returns true if user has admin privileges
|
||||||
|
func IsAdmin() bool {
|
||||||
|
adminDesktop, err := elevate.IsAdminDesktop()
|
||||||
|
if err == nil && adminDesktop {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user