Compare commits
95 Commits
v0.1.0-rc.
...
v0.3.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5546eba36a | ||
|
|
60a9da734f | ||
|
|
852c7c50c0 | ||
|
|
1c2c1a876b | ||
|
|
e5dcd4753e | ||
|
|
765d3a0ad0 | ||
|
|
97e4f9a801 | ||
|
|
d468718d00 | ||
|
|
15e371b592 | ||
|
|
cd9a418df2 | ||
|
|
919f0aa3da | ||
|
|
b59fd50226 | ||
|
|
3c959bb178 | ||
|
|
efbb5acf63 | ||
|
|
b339a9321a | ||
|
|
b045865d6e | ||
|
|
8680f16abd | ||
|
|
98dc5824ce | ||
|
|
0739038d51 | ||
|
|
8ab6eb1cf4 | ||
|
|
30625c68a9 | ||
|
|
fd7282d3cf | ||
|
|
2ad899b066 | ||
|
|
dfa67410b5 | ||
|
|
23f028e65d | ||
|
|
5db130a12e | ||
|
|
9a3fba3fa3 | ||
|
|
0f7ab4354b | ||
|
|
64f2d295a8 | ||
|
|
afb302d5e7 | ||
|
|
9d1ecbbfb2 | ||
|
|
bafa71fc2e | ||
|
|
319632ffe8 | ||
|
|
828410b34c | ||
|
|
4d2b194570 | ||
|
|
a67b9a16af | ||
|
|
6ae27c9a9b | ||
|
|
ff6e369a21 | ||
|
|
5c3b5e7f40 | ||
|
|
8c75ef8bef | ||
|
|
fdc11fff47 | ||
|
|
3dca2d6953 | ||
|
|
6b7d4cf644 | ||
|
|
edd4125742 | ||
|
|
7bf9793f85 | ||
|
|
fcbf980588 | ||
|
|
d08e5efbce | ||
|
|
95ef8547f3 | ||
|
|
ed1e4dfc51 | ||
|
|
4d34fb4e64 | ||
|
|
1fb8b74cd2 | ||
|
|
d040cfed7e | ||
|
|
2c729fe5cc | ||
|
|
e9066b4651 | ||
|
|
673e807528 | ||
|
|
892080bc38 | ||
|
|
2d39f6ccae | ||
|
|
0b2c26847b | ||
|
|
595ea0d4f8 | ||
|
|
f714868fdd | ||
|
|
81821a1f39 | ||
|
|
842b143a48 | ||
|
|
1323a74db0 | ||
|
|
74485d3b13 | ||
|
|
bef3b3392b | ||
|
|
fcea3c99d4 | ||
|
|
96799a25b5 | ||
|
|
07291cdb93 | ||
|
|
21139938c1 | ||
|
|
5cf2d0a6a9 | ||
|
|
8551afe04e | ||
|
|
1685817171 | ||
|
|
e17f662683 | ||
|
|
a764fb870c | ||
|
|
cabff941ac | ||
|
|
b5f35dfb5e | ||
|
|
1d426b7f81 | ||
|
|
e4f9406d44 | ||
|
|
7c79ff62ee | ||
|
|
32c369257b | ||
|
|
08dd719aa1 | ||
|
|
84c714dd93 | ||
|
|
996c8d7c62 | ||
|
|
25e68ce493 | ||
|
|
4881dcbd51 | ||
|
|
d505f70972 | ||
|
|
6a80684378 | ||
|
|
2624a7c4e6 | ||
|
|
9a412e7bf1 | ||
|
|
b5d1690129 | ||
|
|
d4bec15ca3 | ||
|
|
3212aca7c7 | ||
|
|
b97a2251d3 | ||
|
|
528a26ea3e | ||
|
|
13288374f1 |
@@ -1,30 +1,11 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
name: Test
|
||||
name: Test Build On Platforms
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.16.x]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Test
|
||||
run: GOBIN=$(which go) && sudo --preserve-env=GOROOT $GOBIN test -p 1 ./...
|
||||
|
||||
test_build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ windows, linux, darwin ]
|
||||
go-version: [1.16.x]
|
||||
go-version: [1.17.x]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -36,15 +17,15 @@ jobs:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go-test-${{ matrix.os }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
${{ runner.os }}-go-test-${{ matrix.os }}
|
||||
|
||||
- name: Install modules
|
||||
run: go mod tidy
|
||||
run: GOOS=${{ matrix.os }} go mod tidy
|
||||
|
||||
- name: run build client
|
||||
run: GOOS=${{ matrix.os }} go build .
|
||||
@@ -56,4 +37,4 @@ jobs:
|
||||
|
||||
- name: run build signal
|
||||
run: GOOS=${{ matrix.os }} go build .
|
||||
working-directory: signal
|
||||
working-directory: signal
|
||||
29
.github/workflows/golang-test-darwin.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Test Code Darwin
|
||||
on: [push,pull_request]
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.17.x]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: macos-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
macos-go-
|
||||
|
||||
- name: Install modules
|
||||
run: go mod tidy
|
||||
|
||||
- name: Test
|
||||
run: GOBIN=$(which go) && sudo --preserve-env=GOROOT $GOBIN test ./...
|
||||
42
.github/workflows/golang-test-linux.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Test Code Linux
|
||||
on: [push,pull_request]
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.17.x]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: update limits.d
|
||||
run: |
|
||||
cat <<'EOF' | sudo tee -a /etc/security/limits.d/wt.conf
|
||||
root soft nproc 65535
|
||||
root hard nproc 65535
|
||||
root soft nofile 65535
|
||||
root hard nofile 65535
|
||||
$(whoami) soft nproc 65535
|
||||
$(whoami) hard nproc 65535
|
||||
$(whoami) soft nofile 65535
|
||||
$(whoami) hard nofile 65535
|
||||
EOF
|
||||
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install modules
|
||||
run: go mod tidy
|
||||
|
||||
- name: Test
|
||||
run: GOBIN=$(which go) && sudo --preserve-env=GOROOT $GOBIN test ./...
|
||||
51
.github/workflows/golang-test-windows.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Test Code Windows
|
||||
on: [push,pull_request]
|
||||
jobs:
|
||||
pre:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- run: bash -x wireguard_nt.sh
|
||||
working-directory: client
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: syso
|
||||
path: client/*.syso
|
||||
retention-days: 1
|
||||
|
||||
test:
|
||||
needs: pre
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.17.x]
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
%LocalAppData%\go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: syso
|
||||
path: iface\
|
||||
|
||||
- name: Install modules
|
||||
run: go mod tidy
|
||||
|
||||
- name: Test
|
||||
run: go test -tags=load_wgnt_from_rsrc ./...
|
||||
6
.github/workflows/golangci-lint.yml
vendored
@@ -1,9 +1,5 @@
|
||||
name: golangci-lint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
|
||||
39
.github/workflows/release.yml
vendored
@@ -14,11 +14,15 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # It is required for GoReleaser to work properly
|
||||
|
||||
- name: Generate syso with DLL
|
||||
run: bash -x wireguard_nt.sh
|
||||
working-directory: client
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.17
|
||||
-
|
||||
name: Cache Go modules
|
||||
uses: actions/cache@v1
|
||||
@@ -51,28 +55,15 @@ jobs:
|
||||
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 }}
|
||||
|
||||
-
|
||||
id: get_version
|
||||
uses: battila7/get-version-action@v2
|
||||
-
|
||||
name: Install makensis
|
||||
run: sudo apt update && sudo apt install -y nsis nsis-pluginapi
|
||||
-
|
||||
name: Download EnvVar Plugin
|
||||
run: curl -L -o EnVar_plugin.zip https://nsis.sourceforge.io/mediawiki/images/7/7f/EnVar_plugin.zip
|
||||
-
|
||||
name: Extract EnVar plugin
|
||||
run: sudo 7z x -o"/usr/share/nsis/" EnVar_plugin.zip
|
||||
-
|
||||
name: Generate Windows installer
|
||||
run: makensis -V4 client/installer.nsis
|
||||
env:
|
||||
APPVER: ${{ steps.get_version.outputs.major }}.${{ steps.get_version.outputs.minor }}.${{ steps.get_version.outputs.patch }}.${{ github.run_id }}
|
||||
-
|
||||
name: Upload windows installer to release page
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
name: Trigger Windows binaries sign pipeline
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: wiretrustee-installer.exe
|
||||
asset_name: wiretrustee_installer_${{ steps.get_version.outputs.version-without-v }}_windows_amd64.exe
|
||||
tag: ${{ github.ref }}
|
||||
workflow: Sign windows bin and installer
|
||||
repo: wiretrustee/windows-sign-pipeline
|
||||
ref: v0.0.1
|
||||
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
||||
inputs: '{ "tag": "${{ github.ref }}" }'
|
||||
6
.gitignore
vendored
@@ -3,4 +3,8 @@
|
||||
dist/
|
||||
.env
|
||||
conf.json
|
||||
http-cmds.sh
|
||||
http-cmds.sh
|
||||
infrastructure_files/management.json
|
||||
infrastructure_files/docker-compose.yml
|
||||
*.syso
|
||||
client/.distfiles/
|
||||
175
.goreleaser.yaml
@@ -13,15 +13,20 @@ builds:
|
||||
- arm
|
||||
- amd64
|
||||
- arm64
|
||||
- mips
|
||||
gomips:
|
||||
- hardfloat
|
||||
- softfloat
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
ldflags:
|
||||
- -s -w -X github.com/wiretrustee/wiretrustee/client/system.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
tags:
|
||||
- load_wintun_from_rsrc
|
||||
- load_wgnt_from_rsrc
|
||||
|
||||
- id: wiretrustee-mgmt
|
||||
dir: management
|
||||
@@ -32,6 +37,10 @@ builds:
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- arm
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
|
||||
- id: wiretrustee-signal
|
||||
dir: signal
|
||||
@@ -42,29 +51,86 @@ builds:
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- arm
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
archives:
|
||||
- builds:
|
||||
- wiretrustee
|
||||
nfpms:
|
||||
- maintainer: Wiretrustee <wiretrustee@wiretrustee.com>
|
||||
description: Wiretrustee project.
|
||||
- maintainer: Wiretrustee <dev@wiretrustee.com>
|
||||
description: Wiretrustee client.
|
||||
homepage: https://wiretrustee.com/
|
||||
id: deb
|
||||
builds:
|
||||
- wiretrustee
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
contents:
|
||||
- src: release_files/wiretrustee.service
|
||||
dst: /lib/systemd/system/wiretrustee.service
|
||||
|
||||
- src: release_files/wiretrustee.json
|
||||
dst: /etc/wiretrustee/wiretrustee.json
|
||||
type: "config|noreplace"
|
||||
|
||||
scripts:
|
||||
postinstall: "release_files/post_install.sh"
|
||||
preremove: "release_files/pre_remove.sh"
|
||||
|
||||
- maintainer: Wiretrustee <dev@wiretrustee.com>
|
||||
description: Wiretrustee client.
|
||||
homepage: https://wiretrustee.com/
|
||||
id: rpm
|
||||
builds:
|
||||
- wiretrustee
|
||||
formats:
|
||||
- rpm
|
||||
|
||||
scripts:
|
||||
postinstall: "release_files/post_install.sh"
|
||||
preremove: "release_files/pre_remove.sh"
|
||||
dockers:
|
||||
- image_templates:
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-amd64
|
||||
ids:
|
||||
- wiretrustee
|
||||
goarch: amd64
|
||||
use: buildx
|
||||
dockerfile: client/Dockerfile
|
||||
build_flag_templates:
|
||||
- "--platform=linux/amd64"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
- image_templates:
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-arm64v8
|
||||
ids:
|
||||
- wiretrustee
|
||||
goarch: arm64
|
||||
use: buildx
|
||||
dockerfile: client/Dockerfile
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm64"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
- image_templates:
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-arm
|
||||
ids:
|
||||
- wiretrustee
|
||||
goarch: arm
|
||||
goarm: 6
|
||||
use: buildx
|
||||
dockerfile: client/Dockerfile
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
- image_templates:
|
||||
- wiretrustee/signal:{{ .Version }}-amd64
|
||||
ids:
|
||||
@@ -95,6 +161,22 @@ dockers:
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
- image_templates:
|
||||
- wiretrustee/signal:{{ .Version }}-arm
|
||||
ids:
|
||||
- wiretrustee-signal
|
||||
goarch: arm
|
||||
goarm: 6
|
||||
use: buildx
|
||||
dockerfile: signal/Dockerfile
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
- image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-amd64
|
||||
ids:
|
||||
@@ -125,6 +207,22 @@ dockers:
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
- image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-arm
|
||||
ids:
|
||||
- wiretrustee-mgmt
|
||||
goarch: arm
|
||||
goarm: 6
|
||||
use: buildx
|
||||
dockerfile: management/Dockerfile
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
- image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-debug-amd64
|
||||
ids:
|
||||
@@ -156,30 +254,63 @@ dockers:
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
|
||||
- image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-debug-arm
|
||||
ids:
|
||||
- wiretrustee-mgmt
|
||||
goarch: arm
|
||||
goarm: 6
|
||||
use: buildx
|
||||
dockerfile: management/Dockerfile.debug
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
docker_manifests:
|
||||
- name_template: wiretrustee/wiretrustee:{{ .Version }}
|
||||
image_templates:
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-arm64v8
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-arm
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/wiretrustee:latest
|
||||
image_templates:
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-arm64v8
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-arm
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/signal:{{ .Version }}
|
||||
image_templates:
|
||||
- wiretrustee/signal:{{ .Version }}-arm64v8
|
||||
- wiretrustee/signal:{{ .Version }}-arm
|
||||
- wiretrustee/signal:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/signal:latest
|
||||
image_templates:
|
||||
- wiretrustee/signal:{{ .Version }}-arm64v8
|
||||
- wiretrustee/signal:{{ .Version }}-arm
|
||||
- wiretrustee/signal:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/management:{{ .Version }}
|
||||
image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-arm64v8
|
||||
- wiretrustee/management:{{ .Version }}-arm
|
||||
- wiretrustee/management:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/management:latest
|
||||
image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-arm64v8
|
||||
- wiretrustee/management:{{ .Version }}-arm
|
||||
- wiretrustee/management:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/management:debug-latest
|
||||
image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-debug-arm64v8
|
||||
- wiretrustee/management:{{ .Version }}-debug-arm
|
||||
- wiretrustee/management:{{ .Version }}-debug-amd64
|
||||
|
||||
brews:
|
||||
@@ -196,4 +327,20 @@ brews:
|
||||
homepage: https://wiretrustee.com/
|
||||
license: "BSD3"
|
||||
test: |
|
||||
system "#{bin}/{{ .ProjectName }} -h"
|
||||
system "#{bin}/{{ .ProjectName }} -h"
|
||||
|
||||
uploads:
|
||||
- name: debian
|
||||
ids:
|
||||
- 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:
|
||||
- rpm
|
||||
mode: archive
|
||||
target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }}
|
||||
username: dev@wiretrustee.com
|
||||
method: PUT
|
||||
|
||||
1
AUTHORS
@@ -1,2 +1,3 @@
|
||||
Mikhail Bragin (https://github.com/braginini)
|
||||
Maycon Santos (https://github.com/mlsmaycon)
|
||||
Wiretrustee UG (haftungsbeschränkt)
|
||||
|
||||
132
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
dev@wiretrustee.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021 Wiretrustee AUTHORS
|
||||
Copyright (c) 2022 Wiretrustee UG (haftungsbeschränkt) & AUTHORS
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
|
||||
246
README.md
@@ -1,42 +1,72 @@
|
||||
# Wiretrustee
|
||||
<div align="center">
|
||||
|
||||
A WireGuard®-based mesh network that connects your devices into a single private network.
|
||||
<p align="center">
|
||||
<img width="250" src="docs/media/logo-full.png"/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/license-BSD--3-blue" />
|
||||
<img src="https://img.shields.io/docker/pulls/wiretrustee/management" />
|
||||
<img src="https://badgen.net/badge/Open%20Source%3F/Yes%21/blue?icon=github" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<strong>
|
||||
Start using Wiretrustee at <a href="https://app.wiretrustee.com/">app.wiretrustee.com</a>
|
||||
<br/>
|
||||
See <a href="https://docs.wiretrustee.com">Documentation</a>
|
||||
<br/>
|
||||
Join our <a href="https://join.slack.com/t/wiretrustee/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A">Slack channel</a>
|
||||
<br/>
|
||||
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
**Wiretrustee is an open-source VPN platform built on top of WireGuard® 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.
|
||||
|
||||
**Wiretrustee automates Wireguard-based networks, offering a management layer with:**
|
||||
* Centralized Peer IP management with a UI dashboard.
|
||||
* Encrypted peer-to-peer connections without a centralized VPN gateway.
|
||||
* Automatic Peer discovery and configuration.
|
||||
* UDP hole punching to establish peer-to-peer connections behind NAT, firewall, and without a public static IP.
|
||||
* Connection relay fallback in case a peer-to-peer connection is not possible.
|
||||
* Multitenancy (coming soon).
|
||||
* Client application SSO with MFA (coming soon).
|
||||
* Access Controls (coming soon).
|
||||
* Activity Monitoring (coming soon).
|
||||
* Private DNS (coming soon)
|
||||
|
||||
### Secure peer-to-peer VPN in minutes
|
||||
<p float="left" align="middle">
|
||||
<img src="docs/media/peerA.gif" width="400"/>
|
||||
<img src="docs/media/peerB.gif" width="400"/>
|
||||
</p>
|
||||
|
||||
**Note**: The `main` branch may be in an *unstable or even broken state* during development. For stable versions, see [releases](https://github.com/wiretrustee/wiretrustee/releases).
|
||||
|
||||
**Hosted demo version:** [https://beta.wiretrustee.com/](https://beta.wiretrustee.com/peers)
|
||||
|
||||
Please don't use the hosted demonstration version for production purposes.
|
||||
Hosted version:
|
||||
[https://app.wiretrustee.com/](https://app.wiretrustee.com/peers).
|
||||
|
||||
[UI Dashboard Repo](https://github.com/wiretrustee/wiretrustee-dashboard)
|
||||
|
||||
### Why using Wiretrustee?
|
||||
|
||||
* Connect multiple devices to each other via a secure peer-to-peer Wireguard VPN tunnel. At home, the office, or anywhere else.
|
||||
* No need to open ports and expose public IPs on the device, routers etc.
|
||||
* Uses Kernel Wireguard module if available.
|
||||
* Automatic network change detection. When a new peer joins the network others are notified and keys are exchanged automatically.
|
||||
* Automatically reconnects in case of network failures or switches.
|
||||
* Automatic NAT traversal.
|
||||
* Relay server fallback in case of an unsuccessful peer-to-peer connection.
|
||||
* Private key never leaves your device.
|
||||
* Automatic IP address management.
|
||||
* Intuitive UI Dashboard.
|
||||
* Works on ARM devices (e.g. Raspberry Pi).
|
||||
* Open-source (including Management Service)
|
||||
|
||||
### Secure peer-to-peer VPN in minutes
|
||||

|
||||
|
||||
### A bit on Wiretrustee internals
|
||||
* Wiretrustee features a Management Service that offers peer IP management and network updates distribution (e.g. when new peer joins the network).
|
||||
* Wiretrustee features a Management Service that offers peer IP management and network updates distribution (e.g. when a new peer joins the network).
|
||||
* Wiretrustee uses WebRTC ICE implemented in [pion/ice library](https://github.com/pion/ice) to discover connection candidates when establishing a peer-to-peer connection between devices.
|
||||
* Peers negotiate connection through [Signal Service](signal/).
|
||||
* Signal Service uses public Wireguard keys to route messages between peers.
|
||||
Contents of the messages sent between peers through the signaling server are encrypted with Wireguard keys, making it impossible to inspect them.
|
||||
* Occasionally, the NAT-traversal is unsuccessful due to strict NATs (e.g. mobile carrier-grade NAT).
|
||||
When this occurs the system falls back to relay server (TURN), and a secure Wireguard tunnel is established via TURN server.
|
||||
[Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in Wiretrustee setups.
|
||||
* Occasionally, the NAT traversal is unsuccessful due to strict NATs (e.g. mobile carrier-grade NAT). When this occurs the system falls back to the relay server (TURN), and a secure Wireguard tunnel is established via the TURN server. [Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in Wiretrustee setups.
|
||||
|
||||
<p float="left" align="middle">
|
||||
<img src="https://docs.wiretrustee.com/img/architecture/high-level-dia.png" width="700"/>
|
||||
</p>
|
||||
|
||||
|
||||
### Product Roadmap
|
||||
- [Public Roadmap](https://github.com/wiretrustee/wiretrustee/projects/2)
|
||||
@@ -44,125 +74,133 @@ Please don't use the hosted demonstration version for production purposes.
|
||||
|
||||
### Client Installation
|
||||
#### Linux
|
||||
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases)
|
||||
2. Download the latest release (**Switch VERSION to the latest**):
|
||||
|
||||
**Debian packages**
|
||||
```shell
|
||||
wget https://github.com/wiretrustee/wiretrustee/releases/download/v<VERSION>/wiretrustee_<VERSION>_linux_amd64.deb
|
||||
```
|
||||
3. Install the package
|
||||
```shell
|
||||
sudo dpkg -i wiretrustee_<VERSION>_linux_amd64.deb
|
||||
```
|
||||
**Fedora/Centos packages**
|
||||
```shell
|
||||
wget https://github.com/wiretrustee/wiretrustee/releases/download/v<VERSION>/wiretrustee_<VERSION>_linux_amd64.rpm
|
||||
```
|
||||
3. Install the package
|
||||
```shell
|
||||
sudo rpm -i wiretrustee_<VERSION>_linux_amd64.rpm
|
||||
```
|
||||
**APT/Debian**
|
||||
1. Add the repository:
|
||||
```shell
|
||||
sudo apt-get update
|
||||
sudo apt-get install ca-certificates curl gnupg -y
|
||||
curl -L https://pkgs.wiretrustee.com/debian/public.key | sudo apt-key add -
|
||||
echo 'deb https://pkgs.wiretrustee.com/debian stable main' | sudo tee /etc/apt/sources.list.d/wiretrustee.list
|
||||
```
|
||||
2. Install the package
|
||||
```shell
|
||||
sudo apt-get update
|
||||
sudo apt-get install wiretrustee
|
||||
```
|
||||
**RPM/Red hat**
|
||||
1. Add the repository:
|
||||
```shell
|
||||
cat <<EOF | sudo tee /etc/yum.repos.d/wiretrustee.repo
|
||||
[Wiretrustee]
|
||||
name=Wiretrustee
|
||||
baseurl=https://pkgs.wiretrustee.com/yum/
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
gpgkey=https://pkgs.wiretrustee.com/yum/repodata/repomd.xml.key
|
||||
repo_gpgcheck=1
|
||||
EOF
|
||||
```
|
||||
2. Install the package
|
||||
```shell
|
||||
sudo yum install wiretrustee
|
||||
```
|
||||
#### MACOS
|
||||
**Brew install**
|
||||
1. Download and install Brew at https://brew.sh/
|
||||
2. Install the client
|
||||
```shell
|
||||
brew install wiretrustee/client/wiretrustee
|
||||
```
|
||||
```shell
|
||||
brew install wiretrustee/client/wiretrustee
|
||||
```
|
||||
**Installation from binary**
|
||||
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases/latest)
|
||||
2. Download the latest release (**Switch VERSION to the latest**):
|
||||
```shell
|
||||
curl -o ./wiretrustee_<VERSION>_darwin_amd64.tar.gz https://github.com/wiretrustee/wiretrustee/releases/download/v<VERSION>/wiretrustee_<VERSION>_darwin_amd64.tar.gz
|
||||
```
|
||||
```shell
|
||||
curl -o ./wiretrustee_<VERSION>_darwin_amd64.tar.gz https://github.com/wiretrustee/wiretrustee/releases/download/v<VERSION>/wiretrustee_<VERSION>_darwin_amd64.tar.gz
|
||||
```
|
||||
3. Decompress
|
||||
```shell
|
||||
tar xcf ./wiretrustee_<VERSION>_darwin_amd64.tar.gz
|
||||
sudo mv wiretrusee /usr/local/bin/wiretrustee
|
||||
chmod +x /usr/local/bin/wiretrustee
|
||||
```
|
||||
```shell
|
||||
tar xcf ./wiretrustee_<VERSION>_darwin_amd64.tar.gz
|
||||
sudo mv wiretrusee /usr/local/bin/wiretrustee
|
||||
chmod +x /usr/local/bin/wiretrustee
|
||||
```
|
||||
After that you may need to add /usr/local/bin in your MAC's PATH environment variable:
|
||||
````shell
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
````
|
||||
````shell
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
````
|
||||
|
||||
#### Windows
|
||||
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases/latest)
|
||||
2. Download the latest Windows release installer ```wiretrustee_installer_<VERSION>_windows_amd64.exe``` (**Switch VERSION to the latest**):
|
||||
3. Proceed with installation steps
|
||||
4. This will install the client in the C:\\Program Files\\Wiretrustee and add the client service
|
||||
5. After installing you can follow the [Client Configuration](#Client-Configuration) steps.
|
||||
5. After installing, you can follow the [Client Configuration](#Client-Configuration) steps.
|
||||
> To uninstall the client and service, you can use Add/Remove programs
|
||||
|
||||
### Client Configuration
|
||||
1. Login to the Management Service. You need to have a `setup key` in hand (see ).
|
||||
|
||||
For **Unix** systems:
|
||||
```shell
|
||||
sudo wiretrustee up --setup-key <SETUP KEY>
|
||||
```
|
||||
For **Windows** systems, start powershell as administrator and:
|
||||
```shell
|
||||
wiretrustee up --setup-key <SETUP KEY>
|
||||
```
|
||||
For **Docker**, you can run with the following command:
|
||||
```shell
|
||||
sudo wiretrustee login --setup-key <SETUP KEY>
|
||||
docker run --network host --privileged --rm -d -e WT_SETUP_KEY=<SETUP KEY> -v wiretrustee-client:/etc/wiretrustee wiretrustee/wiretrustee:<TAG>
|
||||
```
|
||||
For **Windows** systems:
|
||||
```shell
|
||||
.\wiretrustee.exe login --setup-key <SETUP KEY>
|
||||
```
|
||||
> TAG > 0.3.0 version
|
||||
|
||||
Alternatively, if you are hosting your own Management Service provide `--management-url` property pointing to your Management Service:
|
||||
```shell
|
||||
sudo wiretrustee login --setup-key <SETUP KEY> --management-url https://localhost:33073
|
||||
```
|
||||
```shell
|
||||
sudo wiretrustee up --setup-key <SETUP KEY> --management-url https://localhost:33073
|
||||
```
|
||||
|
||||
You could also omit `--setup-key` property. In this case the tool will prompt it the key.
|
||||
> You could also omit the `--setup-key` property. In this case, the tool will prompt for the key.
|
||||
|
||||
2. Start Wiretrustee:
|
||||
|
||||
For **MACOS** you will just start the service:
|
||||
````shell
|
||||
sudo wiretrustee up
|
||||
# or
|
||||
sudo wiretrustee up & # to run it in background
|
||||
````
|
||||
2. Check your IP:
|
||||
For **MACOS** you will just start the service:
|
||||
````shell
|
||||
sudo ipconfig getifaddr utun100
|
||||
````
|
||||
For **Linux** systems:
|
||||
```shell
|
||||
sudo systemctl restart wiretrustee.service
|
||||
sudo systemctl status wiretrustee.service
|
||||
```
|
||||
```shell
|
||||
ip addr show wt0
|
||||
```
|
||||
For **Windows** systems:
|
||||
```shell
|
||||
.\wiretrustee.exe service start
|
||||
```
|
||||
> You may need to run Powershell as Administrator
|
||||
```shell
|
||||
netsh interface ip show config name="wt0"
|
||||
```
|
||||
|
||||
3. Check your IP:
|
||||
For **MACOS** you will just start the service:
|
||||
````shell
|
||||
sudo ipconfig getifaddr utun100
|
||||
````
|
||||
For **Linux** systems:
|
||||
```shell
|
||||
ip addr show wt0
|
||||
```
|
||||
For **Windows** systems:
|
||||
```shell
|
||||
netsh interface ip show config name="wt0"
|
||||
```
|
||||
3. Repeat on other machines.
|
||||
|
||||
4. Repeat on other machines.
|
||||
|
||||
### Running Management, Signal and Coturn
|
||||
### Running Dashboard, Management, Signal and Coturn
|
||||
Wiretrustee uses [Auth0](https://auth0.com) for user authentication and authorization, therefore you will need to create a free account
|
||||
and configure AUTH0 variables in the compose file (dashboard and management).
|
||||
and configure Auth0 variables in the compose file (dashboard) and in the management config file.
|
||||
We chose Auth0 to "outsource" the user management part of our platform because we believe that implementing a proper user auth is not a trivial task and requires a significant amount of time to make it right. We focused on connectivity instead.
|
||||
It is worth mentioning that the dependency on Auth0 is the only one that cannot be self-hosted.
|
||||
|
||||
Under infrastructure_files we have a docker-compose example to run both, Wiretrustee Management and Signal services, plus an instance of [Coturn](https://github.com/coturn/coturn), it also provides a turnserver.conf file as a simple example of Coturn configuration.
|
||||
Configuring Wiretrustee Auth0 integration:
|
||||
- check [How to run](https://github.com/wiretrustee/wiretrustee-dashboard#how-to-run) to obtain Auth0 environment variables for UI Dashboard
|
||||
- set these variables in the [environment section of the docker-compose file](https://github.com/wiretrustee/wiretrustee/blob/main/infrastructure_files/docker-compose.yml)
|
||||
- check [Auth0 Golang API Guide](https://auth0.com/docs/quickstart/backend/golang) to obtain ```AuthIssuer```, ```AuthAudience```, and ```AuthKeysLocation```
|
||||
- set these properties in the [management config files](https://github.com/wiretrustee/wiretrustee/blob/main/infrastructure_files/management.json#L33)
|
||||
|
||||
|
||||
Under infrastructure_files we have a docker-compose example to run Dashboard, Wiretrustee Management and Signal services, plus an instance of [Coturn](https://github.com/coturn/coturn), it also provides a turnserver.conf file as a simple example of Coturn configuration.
|
||||
You can edit the turnserver.conf file and change its Realm setting (defaults to wiretrustee.com) to your own domain and user setting (defaults to username1:password1) to **proper credentials**.
|
||||
|
||||
The example is set to use the official images from Wiretrustee and Coturn, you can find our documentation to run the signal server in docker in [Running the Signal service](#running-the-signal-service), the management in [Management](./management/README.md), and the Coturn official documentation [here](https://hub.docker.com/r/coturn/coturn).
|
||||
The example is set to use the official images from Wiretrustee and Coturn; you can find our documentation to run the signal server in docker in [Running the Signal service](#running-the-signal-service), the management in [Management](./management/README.md), and the Coturn official documentation [here](https://hub.docker.com/r/coturn/coturn).
|
||||
|
||||
> Run Coturn at your own risk, we are just providing an example, be sure to follow security best practices and to configure proper credentials as this service can be exploited and you may face large data transfer charges.
|
||||
|
||||
Also, if you have an SSL certificate for Coturn, you can modify the docker-compose.yml file to point to its files in your host machine, then switch the domainname to your own SSL domain. If you don't already have an SSL certificate, you can follow [Certbot's](https://certbot.eff.org/docs/intro.html) official documentation
|
||||
Also, if you have an SSL certificate for Coturn, you can modify the docker-compose.yml file to point to its files in your host machine and then switch the domain name to your own SSL domain. If you don't already have an SSL certificate, you can follow [Certbot's](https://certbot.eff.org/docs/intro.html) official documentation
|
||||
to generate one from [Let’s Encrypt](https://letsencrypt.org/), or, we found that the example provided by [BigBlueButton](https://docs.bigbluebutton.org/2.2/setup-turn-server.html#generating-tls-certificates) covers the basics to configure Coturn with Let's Encrypt certs.
|
||||
> The Wiretrustee Management service can generate and maintain the certificates automatically, all you need to do is run the servicein a host with a public IP, configure a valid DNS record pointing to that IP and uncomment the 443 ports and command lines in the docker-compose.yml file.
|
||||
> The Wiretrustee Management service can generate and maintain the certificates automatically, all you need to do is run the service in a host with a public IP, configure a valid DNS record pointing to that IP and uncomment the 443 ports and command lines in the docker-compose.yml file.
|
||||
|
||||
Simple docker-composer execution:
|
||||
````shell
|
||||
|
||||
4
client/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM gcr.io/distroless/base:debug
|
||||
ENV WT_LOG_FILE=console
|
||||
ENTRYPOINT [ "/go/bin/wiretrustee","up"]
|
||||
COPY wiretrustee /go/bin/wiretrustee
|
||||
@@ -4,81 +4,102 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
"github.com/wiretrustee/wiretrustee/client/system"
|
||||
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
||||
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
setupKey string
|
||||
|
||||
loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "login to the Wiretrustee Management Service (first run)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return err
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
var backOff = &backoff.ExponentialBackOff{
|
||||
InitialInterval: time.Second,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 2 * time.Second,
|
||||
MaxElapsedTime: time.Second * 10,
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
|
||||
config, err := internal.GetConfig(managementURL, configPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed getting config %s %v", configPath, err)
|
||||
//os.Exit(ExitSetupFailed)
|
||||
return err
|
||||
loginOp := func() error {
|
||||
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := internal.GetConfig(managementURL, configPath, preSharedKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed getting config %s %v", configPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
//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 err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
mgmTlsEnabled := false
|
||||
if config.ManagementURL.Scheme == "https" {
|
||||
mgmTlsEnabled = true
|
||||
}
|
||||
|
||||
log.Debugf("connecting to Management Service %s", config.ManagementURL.String())
|
||||
mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||
if err != nil {
|
||||
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
|
||||
return err
|
||||
}
|
||||
log.Debugf("connected to management Service %s", config.ManagementURL.String())
|
||||
|
||||
serverKey, err := mgmClient.GetServerPublicKey()
|
||||
if err != nil {
|
||||
log.Errorf("failed while getting Management Service public key: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = loginPeer(*serverKey, mgmClient, setupKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed logging-in peer on Management Service : %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = mgmClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Management Service client: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//validate our peer's Wireguard PRIVATE key
|
||||
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
||||
err := backoff.RetryNotify(loginOp, backOff, func(err error, duration time.Duration) {
|
||||
log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err)
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
||||
//os.Exit(ExitSetupFailed)
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
mgmTlsEnabled := false
|
||||
if config.ManagementURL.Scheme == "https" {
|
||||
mgmTlsEnabled = true
|
||||
}
|
||||
|
||||
log.Debugf("connecting to Management Service %s", config.ManagementURL.String())
|
||||
mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||
if err != nil {
|
||||
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
|
||||
//os.Exit(ExitSetupFailed)
|
||||
return err
|
||||
}
|
||||
log.Debugf("connected to anagement Service %s", config.ManagementURL.String())
|
||||
|
||||
serverKey, err := mgmClient.GetServerPublicKey()
|
||||
if err != nil {
|
||||
log.Errorf("failed while getting Management Service public key: %v", err)
|
||||
//os.Exit(ExitSetupFailed)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = loginPeer(*serverKey, mgmClient, setupKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed logging-in peer on Management Service : %v", err)
|
||||
//os.Exit(ExitSetupFailed)
|
||||
return err
|
||||
}
|
||||
|
||||
err = mgmClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Management Service client: %v", err)
|
||||
//os.Exit(ExitSetupFailed)
|
||||
log.Errorf("exiting login retry loop due to unrecoverable error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -88,7 +109,7 @@ var (
|
||||
)
|
||||
|
||||
// loginPeer attempts to login to Management Service. If peer wasn't registered, tries the registration flow.
|
||||
func loginPeer(serverPublicKey wgtypes.Key, client *mgm.Client, setupKey string) (*mgmProto.LoginResponse, error) {
|
||||
func loginPeer(serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string) (*mgmProto.LoginResponse, error) {
|
||||
|
||||
loginResp, err := client.Login(serverPublicKey)
|
||||
if err != nil {
|
||||
@@ -107,7 +128,7 @@ func loginPeer(serverPublicKey wgtypes.Key, client *mgm.Client, setupKey string)
|
||||
|
||||
// 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.
|
||||
func registerPeer(serverPublicKey wgtypes.Key, client *mgm.Client, setupKey string) (*mgmProto.LoginResponse, error) {
|
||||
func registerPeer(serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string) (*mgmProto.LoginResponse, error) {
|
||||
|
||||
var err error
|
||||
if setupKey == "" {
|
||||
@@ -124,7 +145,8 @@ func registerPeer(serverPublicKey wgtypes.Key, client *mgm.Client, setupKey stri
|
||||
}
|
||||
|
||||
log.Debugf("sending peer registration request to Management Service")
|
||||
loginResp, err := client.Register(serverPublicKey, validSetupKey.String())
|
||||
info := system.GetInfo()
|
||||
loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), info)
|
||||
if err != nil {
|
||||
log.Errorf("failed registering peer %v", err)
|
||||
return nil, err
|
||||
@@ -151,7 +173,3 @@ func promptPeerSetupKey() (string, error) {
|
||||
|
||||
return "", s.Err()
|
||||
}
|
||||
|
||||
func init() {
|
||||
loginCmd.PersistentFlags().StringVar(&setupKey, "setup-key", "", "Setup key obtained from the Management Service Dashboard (used to register peer)")
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestLogin(t *testing.T) {
|
||||
}
|
||||
|
||||
if actualConf.WgIface != iface.WgInterfaceDefault {
|
||||
t.Errorf("expected WgIface %s got %s", iface.WgInterfaceDefault, actualConf.WgIface)
|
||||
t.Errorf("expected WgIfaceName %s got %s", iface.WgInterfaceDefault, actualConf.WgIface)
|
||||
}
|
||||
|
||||
if len(actualConf.PrivateKey) == 0 {
|
||||
|
||||
@@ -2,17 +2,15 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
// ExitSetupFailed defines exit code
|
||||
ExitSetupFailed = 1
|
||||
DefaultConfigPath = ""
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,15 +20,17 @@ var (
|
||||
defaultLogFile string
|
||||
logFile string
|
||||
managementURL string
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
setupKey string
|
||||
preSharedKey string
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "wiretrustee",
|
||||
Short: "",
|
||||
Long: "",
|
||||
}
|
||||
|
||||
// Execution control channel for stopCh signal
|
||||
stopCh chan int
|
||||
stopCh chan int
|
||||
cleanupCh chan struct{}
|
||||
)
|
||||
|
||||
// Execute executes the root command.
|
||||
@@ -40,6 +40,7 @@ func Execute() error {
|
||||
func init() {
|
||||
|
||||
stopCh = make(chan int)
|
||||
cleanupCh = make(chan struct{})
|
||||
|
||||
defaultConfigPath = "/etc/wiretrustee/config.json"
|
||||
defaultLogFile = "/var/log/wiretrustee/client.log"
|
||||
@@ -52,9 +53,12 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVar(&configPath, "config", defaultConfigPath, "Wiretrustee config file location")
|
||||
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "sets Wiretrustee log level")
|
||||
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Wiretrustee log path. If console is specified the the log will be output to stdout")
|
||||
rootCmd.PersistentFlags().StringVar(&setupKey, "setup-key", "", "Setup key obtained from the Management Service Dashboard (used to register peer)")
|
||||
rootCmd.PersistentFlags().StringVar(&preSharedKey, "preshared-key", "", "Sets Wireguard PreSharedKey property. If set, then only peers that have the same key can communicate.")
|
||||
rootCmd.AddCommand(serviceCmd)
|
||||
rootCmd.AddCommand(upCmd)
|
||||
rootCmd.AddCommand(loginCmd)
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service
|
||||
serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service
|
||||
}
|
||||
@@ -62,11 +66,36 @@ func init() {
|
||||
// SetupCloseHandler handles SIGTERM signal and exits with success
|
||||
func SetupCloseHandler() {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
for range c {
|
||||
fmt.Println("\r- Ctrl+C pressed in Terminal")
|
||||
log.Info("shutdown signal received")
|
||||
stopCh <- 0
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// SetFlagsFromEnvVars reads and updates flag values from environment variables with prefix WT_
|
||||
func SetFlagsFromEnvVars() {
|
||||
flags := rootCmd.PersistentFlags()
|
||||
flags.VisitAll(func(f *pflag.Flag) {
|
||||
|
||||
envVar := FlagNameToEnvVar(f.Name)
|
||||
|
||||
if value, present := os.LookupEnv(envVar); present {
|
||||
err := flags.Set(f.Name, value)
|
||||
if err != nil {
|
||||
log.Infof("unable to configure flag %s using variable %s, err: %v", f.Name, envVar, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FlagNameToEnvVar converts flag name to environment var name adding a prefix,
|
||||
// replacing dashes and making all uppercase (e.g. setup-keys is converted to WT_SETUP_KEYS)
|
||||
func FlagNameToEnvVar(f string) string {
|
||||
prefix := "WT_"
|
||||
parsed := strings.ReplaceAll(f, "-", "_")
|
||||
upper := strings.ToUpper(parsed)
|
||||
return prefix + upper
|
||||
}
|
||||
|
||||
@@ -34,6 +34,3 @@ var (
|
||||
Short: "manages wiretrustee service",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
}
|
||||
|
||||
@@ -4,23 +4,35 @@ import (
|
||||
"github.com/kardianos/service"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (p *program) Start(s service.Service) error {
|
||||
func (p *program) Start(service.Service) error {
|
||||
|
||||
// Start should not block. Do the actual work async.
|
||||
log.Info("starting service") //nolint
|
||||
go func() {
|
||||
err := upCmd.RunE(p.cmd, p.args)
|
||||
err := runClient()
|
||||
if err != nil {
|
||||
log.Errorf("stopped Wiretrustee client app due to error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *program) Stop(s service.Service) error {
|
||||
stopCh <- 1
|
||||
func (p *program) Stop(service.Service) error {
|
||||
go func() {
|
||||
stopCh <- 1
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-cleanupCh:
|
||||
case <-time.After(time.Second * 10):
|
||||
log.Warnf("failed waiting for service cleanup, terminating")
|
||||
}
|
||||
log.Info("stopped Wiretrustee service") //nolint
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -29,6 +41,15 @@ var (
|
||||
Use: "run",
|
||||
Short: "runs wiretrustee as service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
SetupCloseHandler()
|
||||
|
||||
prg := &program{
|
||||
cmd: cmd,
|
||||
@@ -54,19 +75,26 @@ var (
|
||||
startCmd = &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "starts wiretrustee service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return err
|
||||
}
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
err = s.Start()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
cmd.Printf("Wiretrustee service has been started")
|
||||
cmd.Println("Wiretrustee service has been started")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -76,7 +104,12 @@ var (
|
||||
Use: "stop",
|
||||
Short: "stops wiretrustee service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
}
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
@@ -87,7 +120,7 @@ var (
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Printf("Wiretrustee service has been stopped")
|
||||
cmd.Println("Wiretrustee service has been stopped")
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -97,7 +130,12 @@ var (
|
||||
Use: "restart",
|
||||
Short: "restarts wiretrustee service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
}
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
@@ -108,10 +146,7 @@ var (
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Printf("Wiretrustee service has been restarted")
|
||||
cmd.Println("Wiretrustee service has been restarted")
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ var (
|
||||
installCmd = &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "installs wiretrustee service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
svcConfig := newSVCConfig()
|
||||
|
||||
@@ -30,15 +31,16 @@ var (
|
||||
s, err := newSVC(&program{}, svcConfig)
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Install()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
cmd.Printf("Wiretrustee service has been installed")
|
||||
cmd.Println("Wiretrustee service has been installed")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -48,6 +50,7 @@ var (
|
||||
Use: "uninstall",
|
||||
Short: "uninstalls wiretrustee service from system",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
@@ -60,10 +63,7 @@ var (
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Printf("Wiretrustee has been uninstalled")
|
||||
cmd.Println("Wiretrustee has been uninstalled")
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Liste
|
||||
}
|
||||
|
||||
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
||||
accountManager := mgmt.NewManager(store, peersUpdateManager)
|
||||
accountManager := mgmt.NewManager(store, peersUpdateManager, nil)
|
||||
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
||||
if err != nil {
|
||||
|
||||
239
client/cmd/up.go
@@ -2,13 +2,16 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/kardianos/service"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
||||
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
signal "github.com/wiretrustee/wiretrustee/signal/client"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -17,112 +20,73 @@ import (
|
||||
var (
|
||||
upCmd = &cobra.Command{
|
||||
Use: "up",
|
||||
Short: "start wiretrustee",
|
||||
Short: "install, login and start wiretrustee client",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
err := loginCmd.RunE(cmd, args)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return err
|
||||
}
|
||||
if logFile == "console" {
|
||||
SetupCloseHandler()
|
||||
return runClient()
|
||||
}
|
||||
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := internal.ReadConfig(managementURL, configPath)
|
||||
srvStatus, err := s.Status()
|
||||
if err != nil {
|
||||
log.Errorf("failed reading config %s %v", configPath, err)
|
||||
return err
|
||||
if err == service.ErrNotInstalled {
|
||||
log.Infof("%s. Installing it now", err.Error())
|
||||
e := installCmd.RunE(cmd, args)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
} else {
|
||||
log.Warnf("failed retrieving service status: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
//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 err
|
||||
if srvStatus == service.StatusRunning {
|
||||
stopCmd.Run(cmd, args)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
mgmTlsEnabled := false
|
||||
if config.ManagementURL.Scheme == "https" {
|
||||
mgmTlsEnabled = true
|
||||
}
|
||||
|
||||
// connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config
|
||||
mgmClient, loginResp, err := connectToManagement(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
|
||||
signalClient, err := connectToSignal(ctx, loginResp.GetWiretrusteeConfig(), myPrivateKey)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
engineConfig, err := createEngineConfig(myPrivateKey, config, loginResp.GetWiretrusteeConfig(), loginResp.GetPeerConfig())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// create start the Wiretrustee Engine that will connect to the Signal and Management streams and manage connections to remote peers.
|
||||
engine := internal.NewEngine(signalClient, mgmClient, engineConfig, cancel)
|
||||
err = engine.Start()
|
||||
if err != nil {
|
||||
log.Errorf("error while starting Wiretrustee Connection Engine: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
SetupCloseHandler()
|
||||
|
||||
select {
|
||||
case <-stopCh:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
log.Infof("receive signal to stop running")
|
||||
err = mgmClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Management Service client %v", err)
|
||||
return err
|
||||
}
|
||||
err = signalClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Signal Service client %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = engine.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("failed stopping engine %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return startCmd.RunE(cmd, args)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
}
|
||||
|
||||
// createEngineConfig converts configuration received from Management Service to EngineConfig
|
||||
func createEngineConfig(key wgtypes.Key, config *internal.Config, wtConfig *mgmProto.WiretrusteeConfig, peerConfig *mgmProto.PeerConfig) (*internal.EngineConfig, error) {
|
||||
func createEngineConfig(key wgtypes.Key, config *internal.Config, peerConfig *mgmProto.PeerConfig) (*internal.EngineConfig, error) {
|
||||
iFaceBlackList := make(map[string]struct{})
|
||||
for i := 0; i < len(config.IFaceBlackList); i += 2 {
|
||||
iFaceBlackList[config.IFaceBlackList[i]] = struct{}{}
|
||||
}
|
||||
|
||||
return &internal.EngineConfig{
|
||||
WgIface: config.WgIface,
|
||||
engineConf := &internal.EngineConfig{
|
||||
WgIfaceName: config.WgIface,
|
||||
WgAddr: peerConfig.Address,
|
||||
IFaceBlackList: iFaceBlackList,
|
||||
WgPrivateKey: key,
|
||||
}, nil
|
||||
WgPort: internal.WgPort,
|
||||
}
|
||||
|
||||
if config.PreSharedKey != "" {
|
||||
preSharedKey, err := wgtypes.ParseKey(config.PreSharedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
engineConf.PreSharedKey = &preSharedKey
|
||||
}
|
||||
|
||||
return engineConf, nil
|
||||
}
|
||||
|
||||
// connectToSignal creates Signal Service client and established a connection
|
||||
func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, ourPrivateKey wgtypes.Key) (*signal.Client, error) {
|
||||
func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, ourPrivateKey wgtypes.Key) (*signal.GrpcClient, error) {
|
||||
var sigTLSEnabled bool
|
||||
if wtConfig.Signal.Protocol == mgmProto.HostConfig_HTTPS {
|
||||
sigTLSEnabled = true
|
||||
@@ -140,7 +104,7 @@ func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig,
|
||||
}
|
||||
|
||||
// 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.Client, *mgmProto.LoginResponse, error) {
|
||||
func connectToManagement(ctx context.Context, managementAddr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*mgm.GrpcClient, *mgmProto.LoginResponse, error) {
|
||||
log.Debugf("connecting to management server %s", managementAddr)
|
||||
client, err := mgm.NewClient(ctx, managementAddr, ourPrivateKey, tlsEnabled)
|
||||
if err != nil {
|
||||
@@ -163,7 +127,112 @@ func connectToManagement(ctx context.Context, managementAddr string, ourPrivateK
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("peer logged in to Management Service %s", managementAddr)
|
||||
log.Debugf("peer logged in to Management Service %s", managementAddr)
|
||||
|
||||
return client, loginResp, nil
|
||||
}
|
||||
|
||||
func runClient() error {
|
||||
var backOff = &backoff.ExponentialBackOff{
|
||||
InitialInterval: time.Second,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 10 * time.Second,
|
||||
MaxElapsedTime: 24 * 3 * time.Hour, //stop the client after 3 days trying (must be a huge problem, e.g permission denied)
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
|
||||
operation := func() error {
|
||||
|
||||
config, err := internal.ReadConfig(managementURL, configPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed reading config %s %v", configPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
//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 err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
mgmTlsEnabled := false
|
||||
if config.ManagementURL.Scheme == "https" {
|
||||
mgmTlsEnabled = true
|
||||
}
|
||||
|
||||
// connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config
|
||||
mgmClient, loginResp, err := connectToManagement(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
|
||||
signalClient, err := connectToSignal(ctx, loginResp.GetWiretrusteeConfig(), myPrivateKey)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
peerConfig := loginResp.GetPeerConfig()
|
||||
|
||||
engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
engine := internal.NewEngine(signalClient, mgmClient, engineConfig, cancel, ctx)
|
||||
err = engine.Start()
|
||||
if err != nil {
|
||||
log.Errorf("error while starting Wiretrustee Connection Engine: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Print("Wiretrustee engine started, my IP is: ", peerConfig.Address)
|
||||
|
||||
select {
|
||||
case <-stopCh:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
backOff.Reset()
|
||||
|
||||
err = mgmClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Management Service client %v", err)
|
||||
return err
|
||||
}
|
||||
err = signalClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Signal Service client %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = engine.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("failed stopping engine %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
cleanupCh <- struct{}{}
|
||||
}()
|
||||
|
||||
log.Info("stopped Wiretrustee client")
|
||||
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, backOff)
|
||||
if err != nil {
|
||||
log.Errorf("exiting client retry loop due to unrecoverable error: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -37,27 +34,9 @@ func TestUp_Start(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestUp_ShouldFail_On_NoConfig(t *testing.T) {
|
||||
|
||||
tempDir := t.TempDir()
|
||||
confPath := tempDir + "/config.json"
|
||||
mgmtURL := fmt.Sprintf("http://%s", mgmAddr)
|
||||
rootCmd.SetArgs([]string{
|
||||
"up",
|
||||
"--config",
|
||||
confPath,
|
||||
"--management-url",
|
||||
mgmtURL,
|
||||
})
|
||||
err := rootCmd.Execute()
|
||||
if err == nil || !errors.Is(err, os.ErrNotExist) {
|
||||
t.Errorf("expecting login command to fail on absence of config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUp(t *testing.T) {
|
||||
|
||||
defer iface.Close()
|
||||
//defer iface.Close("wt0")
|
||||
|
||||
tempDir := t.TempDir()
|
||||
confPath := tempDir + "/config.json"
|
||||
@@ -65,24 +44,19 @@ func TestUp(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rootCmd.SetArgs([]string{
|
||||
"login",
|
||||
"up",
|
||||
"--config",
|
||||
confPath,
|
||||
"--setup-key",
|
||||
"A2C8E62B-38F5-4553-B31E-DD66C696CEBB",
|
||||
"--management-url",
|
||||
mgmtURL.String(),
|
||||
})
|
||||
err = rootCmd.Execute()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rootCmd.SetArgs([]string{
|
||||
"up",
|
||||
"--config",
|
||||
confPath,
|
||||
"--log-level",
|
||||
"debug",
|
||||
"--log-file",
|
||||
"console",
|
||||
})
|
||||
go func() {
|
||||
err = rootCmd.Execute()
|
||||
@@ -91,20 +65,23 @@ func TestUp(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
exists := false
|
||||
for start := time.Now(); time.Since(start) < 15*time.Second; {
|
||||
timeout := 15 * time.Second
|
||||
timeoutChannel := time.After(timeout)
|
||||
for {
|
||||
select {
|
||||
case <-timeoutChannel:
|
||||
t.Fatalf("expected wireguard interface %s to be created before %s", iface.WgInterfaceDefault, timeout.String())
|
||||
default:
|
||||
}
|
||||
e, err := iface.Exists(iface.WgInterfaceDefault)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if *e {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if !exists {
|
||||
t.Errorf("expected wireguard interface %s to be created", iface.WgInterfaceDefault)
|
||||
}
|
||||
}
|
||||
|
||||
16
client/cmd/version.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wiretrustee/wiretrustee/client/system"
|
||||
)
|
||||
|
||||
var (
|
||||
versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "prints wiretrustee version",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Println(system.WiretrusteeVersion())
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -106,6 +106,7 @@ SectionEnd
|
||||
Section Uninstall
|
||||
${INSTALL_TYPE}
|
||||
|
||||
Exec '"$INSTDIR\${MAIN_APP_EXE}" service stop'
|
||||
Exec '"$INSTDIR\${MAIN_APP_EXE}" service uninstall'
|
||||
# wait the service uninstall take unblock the executable
|
||||
Sleep 3000
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package internal
|
||||
|
||||
import "sync"
|
||||
|
||||
// A Cond is a condition variable like sync.Cond, but using a channel so we can use select.
|
||||
type Cond struct {
|
||||
once sync.Once
|
||||
C chan struct{}
|
||||
}
|
||||
|
||||
// NewCond creates a new condition variable.
|
||||
func NewCond() *Cond {
|
||||
return &Cond{C: make(chan struct{})}
|
||||
}
|
||||
|
||||
// Do runs f if the condition hasn't been signaled yet. Afterwards it will be signaled.
|
||||
func (c *Cond) Do(f func()) {
|
||||
c.once.Do(func() {
|
||||
f()
|
||||
close(c.C)
|
||||
})
|
||||
}
|
||||
|
||||
// Signal closes the condition variable channel.
|
||||
func (c *Cond) Signal() {
|
||||
c.Do(func() {})
|
||||
}
|
||||
|
||||
// Wait waits for the condition variable channel to close.
|
||||
func (c *Cond) Wait() {
|
||||
<-c.C
|
||||
}
|
||||
@@ -28,13 +28,14 @@ func init() {
|
||||
type Config struct {
|
||||
// Wireguard private key of local peer
|
||||
PrivateKey string
|
||||
PreSharedKey string
|
||||
ManagementURL *url.URL
|
||||
WgIface string
|
||||
IFaceBlackList []string
|
||||
}
|
||||
|
||||
//createNewConfig creates a new config generating a new Wireguard key and saving to file
|
||||
func createNewConfig(managementURL string, configPath string) (*Config, error) {
|
||||
func createNewConfig(managementURL string, configPath string, preSharedKey string) (*Config, error) {
|
||||
wgKey := generateKey()
|
||||
config := &Config{PrivateKey: wgKey, WgIface: iface.WgInterfaceDefault, IFaceBlackList: []string{}}
|
||||
if managementURL != "" {
|
||||
@@ -47,6 +48,10 @@ func createNewConfig(managementURL string, configPath string) (*Config, error) {
|
||||
config.ManagementURL = managementURLDefault
|
||||
}
|
||||
|
||||
if preSharedKey != "" {
|
||||
config.PreSharedKey = preSharedKey
|
||||
}
|
||||
|
||||
config.IFaceBlackList = []string{iface.WgInterfaceDefault, "tun0"}
|
||||
|
||||
err := util.WriteJson(configPath, config)
|
||||
@@ -93,11 +98,11 @@ func ReadConfig(managementURL string, configPath string) (*Config, error) {
|
||||
}
|
||||
|
||||
// GetConfig reads existing config or generates a new one
|
||||
func GetConfig(managementURL string, configPath string) (*Config, error) {
|
||||
func GetConfig(managementURL string, configPath string, preSharedKey string) (*Config, error) {
|
||||
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
log.Infof("generating new config %s", configPath)
|
||||
return createNewConfig(managementURL, configPath)
|
||||
return createNewConfig(managementURL, configPath, preSharedKey)
|
||||
} else {
|
||||
return ReadConfig(managementURL, configPath)
|
||||
}
|
||||
@@ -105,7 +110,7 @@ func GetConfig(managementURL string, configPath string) (*Config, error) {
|
||||
|
||||
// generateKey generates a new Wireguard private key
|
||||
func generateKey() string {
|
||||
key, err := wgtypes.GenerateKey()
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -1,409 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
ice "github.com/pion/ice/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultWgKeepAlive default Wireguard keep alive constant
|
||||
DefaultWgKeepAlive = 20 * time.Second
|
||||
privateIPBlocks []*net.IPNet
|
||||
)
|
||||
|
||||
type Status string
|
||||
|
||||
const (
|
||||
StatusConnected Status = "Connected"
|
||||
StatusConnecting Status = "Connecting"
|
||||
StatusDisconnected Status = "Disconnected"
|
||||
)
|
||||
|
||||
func init() {
|
||||
for _, cidr := range []string{
|
||||
"127.0.0.0/8", // IPv4 loopback
|
||||
"10.0.0.0/8", // RFC1918
|
||||
"172.16.0.0/12", // RFC1918
|
||||
"192.168.0.0/16", // RFC1918
|
||||
"169.254.0.0/16", // RFC3927 link-local
|
||||
"::1/128", // IPv6 loopback
|
||||
"fe80::/10", // IPv6 link-local
|
||||
"fc00::/7", // IPv6 unique local addr
|
||||
} {
|
||||
_, block, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("parse error on %q: %v", cidr, err))
|
||||
}
|
||||
privateIPBlocks = append(privateIPBlocks, block)
|
||||
}
|
||||
}
|
||||
|
||||
// ConnConfig Connection configuration struct
|
||||
type ConnConfig struct {
|
||||
// Local Wireguard listening address e.g. 127.0.0.1:51820
|
||||
WgListenAddr string
|
||||
// A Local Wireguard Peer IP address in CIDR notation e.g. 10.30.30.1/24
|
||||
WgPeerIP string
|
||||
// Local Wireguard Interface name (e.g. wg0)
|
||||
WgIface string
|
||||
// Wireguard allowed IPs (e.g. 10.30.30.2/32)
|
||||
WgAllowedIPs string
|
||||
// Local Wireguard private key
|
||||
WgKey wgtypes.Key
|
||||
// Remote Wireguard public key
|
||||
RemoteWgKey wgtypes.Key
|
||||
|
||||
StunTurnURLS []*ice.URL
|
||||
|
||||
iFaceBlackList map[string]struct{}
|
||||
}
|
||||
|
||||
// IceCredentials ICE protocol credentials struct
|
||||
type IceCredentials struct {
|
||||
uFrag string
|
||||
pwd string
|
||||
}
|
||||
|
||||
// Connection Holds information about a connection and handles signal protocol
|
||||
type Connection struct {
|
||||
Config ConnConfig
|
||||
// signalCandidate is a handler function to signal remote peer about local connection candidate
|
||||
signalCandidate func(candidate ice.Candidate) error
|
||||
|
||||
// signalOffer is a handler function to signal remote peer our connection offer (credentials)
|
||||
signalOffer func(uFrag string, pwd string) error
|
||||
|
||||
// signalOffer is a handler function to signal remote peer our connection answer (credentials)
|
||||
signalAnswer func(uFrag string, pwd string) error
|
||||
|
||||
// remoteAuthChannel is a channel used to wait for remote credentials to proceed with the connection
|
||||
remoteAuthChannel chan IceCredentials
|
||||
|
||||
// agent is an actual ice.Agent that is used to negotiate and maintain a connection to a remote peer
|
||||
agent *ice.Agent
|
||||
|
||||
wgProxy *WgProxy
|
||||
|
||||
connected *Cond
|
||||
closeCond *Cond
|
||||
|
||||
remoteAuthCond sync.Once
|
||||
|
||||
Status Status
|
||||
}
|
||||
|
||||
// NewConnection Creates a new connection and sets handling functions for signal protocol
|
||||
func NewConnection(config ConnConfig,
|
||||
signalCandidate func(candidate ice.Candidate) error,
|
||||
signalOffer func(uFrag string, pwd string) error,
|
||||
signalAnswer func(uFrag string, pwd string) error,
|
||||
) *Connection {
|
||||
|
||||
return &Connection{
|
||||
Config: config,
|
||||
signalCandidate: signalCandidate,
|
||||
signalOffer: signalOffer,
|
||||
signalAnswer: signalAnswer,
|
||||
remoteAuthChannel: make(chan IceCredentials, 1),
|
||||
closeCond: NewCond(),
|
||||
connected: NewCond(),
|
||||
agent: nil,
|
||||
wgProxy: NewWgProxy(config.WgIface, config.RemoteWgKey.String(), config.WgAllowedIPs, config.WgListenAddr),
|
||||
Status: StatusDisconnected,
|
||||
}
|
||||
}
|
||||
|
||||
// Open opens connection to a remote peer.
|
||||
// Will block until the connection has successfully established
|
||||
func (conn *Connection) Open(timeout time.Duration) error {
|
||||
|
||||
// create an ice.Agent that will be responsible for negotiating and establishing actual peer-to-peer connection
|
||||
a, err := ice.NewAgent(&ice.AgentConfig{
|
||||
// MulticastDNSMode: ice.MulticastDNSModeQueryAndGather,
|
||||
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4},
|
||||
Urls: conn.Config.StunTurnURLS,
|
||||
CandidateTypes: []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay},
|
||||
InterfaceFilter: func(s string) bool {
|
||||
if conn.Config.iFaceBlackList == nil {
|
||||
return true
|
||||
}
|
||||
_, ok := conn.Config.iFaceBlackList[s]
|
||||
return !ok
|
||||
},
|
||||
})
|
||||
conn.agent = a
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.listenOnLocalCandidates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.listenOnConnectionStateChanges()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.signalCredentials()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.Status = StatusConnecting
|
||||
log.Infof("trying to connect to peer %s", conn.Config.RemoteWgKey.String())
|
||||
|
||||
// wait until credentials have been sent from the remote peer (will arrive via a signal server)
|
||||
select {
|
||||
case remoteAuth := <-conn.remoteAuthChannel:
|
||||
|
||||
log.Infof("got a connection confirmation from peer %s", conn.Config.RemoteWgKey.String())
|
||||
|
||||
err = conn.agent.GatherCandidates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isControlling := conn.Config.WgKey.PublicKey().String() > conn.Config.RemoteWgKey.String()
|
||||
var remoteConn *ice.Conn
|
||||
remoteConn, err = conn.openConnectionToRemote(isControlling, remoteAuth)
|
||||
if err != nil {
|
||||
log.Errorf("failed establishing connection with the remote peer %s %s", conn.Config.RemoteWgKey.String(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
var pair *ice.CandidatePair
|
||||
pair, err = conn.agent.GetSelectedCandidatePair()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// in case the remote peer is in the local network or one of the peers has public static IP -> no need for a Wireguard proxy, direct communication is possible.
|
||||
if !useProxy(pair) {
|
||||
log.Debugf("it is possible to establish a direct connection (without proxy) to peer %s - my addr: %s, remote addr: %s", conn.Config.RemoteWgKey.String(), pair.Local.Address(), pair.Remote.Address())
|
||||
err = conn.wgProxy.StartLocal(fmt.Sprintf("%s:%d", pair.Remote.Address(), iface.WgPort))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Infof("establishing secure tunnel to peer %s via selected candidate pair %s", conn.Config.RemoteWgKey.String(), pair)
|
||||
err = conn.wgProxy.Start(remoteConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pair.Remote.Type() == ice.CandidateTypeRelay || pair.Local.Type() == ice.CandidateTypeRelay {
|
||||
log.Infof("using relay with peer %s", conn.Config.RemoteWgKey)
|
||||
}
|
||||
|
||||
conn.Status = StatusConnected
|
||||
log.Infof("opened connection to peer %s", conn.Config.RemoteWgKey.String())
|
||||
case <-conn.closeCond.C:
|
||||
conn.Status = StatusDisconnected
|
||||
return fmt.Errorf("connection to peer %s has been closed", conn.Config.RemoteWgKey.String())
|
||||
case <-time.After(timeout):
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
log.Warnf("error while closing connection to peer %s -> %s", conn.Config.RemoteWgKey.String(), err.Error())
|
||||
}
|
||||
conn.Status = StatusDisconnected
|
||||
return fmt.Errorf("timeout of %vs exceeded while waiting for the remote peer %s", timeout.Seconds(), conn.Config.RemoteWgKey.String())
|
||||
}
|
||||
|
||||
// wait until connection has been closed
|
||||
<-conn.closeCond.C
|
||||
conn.Status = StatusDisconnected
|
||||
return fmt.Errorf("connection to peer %s has been closed", conn.Config.RemoteWgKey.String())
|
||||
}
|
||||
|
||||
func isPublicIP(ip net.IP) bool {
|
||||
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, block := range privateIPBlocks {
|
||||
if block.Contains(ip) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//useProxy determines whether a direct connection (without a go proxy) is possible
|
||||
//There are 2 cases: one of the peers has a public IP or both peers are in teh same private network.
|
||||
//Please note, that this check happens when peers were already able to ping each other with ICE layer.
|
||||
func useProxy(pair *ice.CandidatePair) bool {
|
||||
remoteIP := net.ParseIP(pair.Remote.Address())
|
||||
myIp := net.ParseIP(pair.Local.Address())
|
||||
if pair.Local.Type() == ice.CandidateTypeHost && pair.Remote.Type() == ice.CandidateTypeHost {
|
||||
if isPublicIP(remoteIP) || isPublicIP(myIp) {
|
||||
//one of the hosts has a public IP
|
||||
return false
|
||||
}
|
||||
|
||||
if !isPublicIP(remoteIP) && !isPublicIP(myIp) {
|
||||
//both hosts are in the same private network
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Close Closes a peer connection
|
||||
func (conn *Connection) Close() error {
|
||||
var err error
|
||||
conn.closeCond.Do(func() {
|
||||
|
||||
log.Warnf("closing connection to peer %s", conn.Config.RemoteWgKey.String())
|
||||
|
||||
if a := conn.agent; a != nil {
|
||||
e := a.Close()
|
||||
if e != nil {
|
||||
log.Warnf("error while closing ICE agent of peer connection %s", conn.Config.RemoteWgKey.String())
|
||||
err = e
|
||||
}
|
||||
}
|
||||
|
||||
if c := conn.wgProxy; c != nil {
|
||||
e := c.Close()
|
||||
if e != nil {
|
||||
log.Warnf("error while closingWireguard proxy connection of peer connection %s", conn.Config.RemoteWgKey.String())
|
||||
err = e
|
||||
}
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// OnAnswer Handles the answer from the other peer
|
||||
func (conn *Connection) OnAnswer(remoteAuth IceCredentials) error {
|
||||
|
||||
conn.remoteAuthCond.Do(func() {
|
||||
log.Debugf("OnAnswer from peer %s", conn.Config.RemoteWgKey.String())
|
||||
conn.remoteAuthChannel <- remoteAuth
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnOffer Handles the offer from the other peer
|
||||
func (conn *Connection) OnOffer(remoteAuth IceCredentials) error {
|
||||
|
||||
conn.remoteAuthCond.Do(func() {
|
||||
log.Debugf("OnOffer from peer %s", conn.Config.RemoteWgKey.String())
|
||||
conn.remoteAuthChannel <- remoteAuth
|
||||
uFrag, pwd, err := conn.agent.GetLocalUserCredentials()
|
||||
if err != nil { //nolint
|
||||
}
|
||||
|
||||
err = conn.signalAnswer(uFrag, pwd)
|
||||
if err != nil { //nolint
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnRemoteCandidate Handles remote candidate provided by the peer.
|
||||
func (conn *Connection) OnRemoteCandidate(candidate ice.Candidate) error {
|
||||
|
||||
log.Debugf("onRemoteCandidate from peer %s -> %s", conn.Config.RemoteWgKey.String(), candidate.String())
|
||||
|
||||
err := conn.agent.AddRemoteCandidate(candidate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// openConnectionToRemote opens an ice.Conn to the remote peer. This is a real peer-to-peer connection
|
||||
// blocks until connection has been established
|
||||
func (conn *Connection) openConnectionToRemote(isControlling bool, credentials IceCredentials) (*ice.Conn, error) {
|
||||
var realConn *ice.Conn
|
||||
var err error
|
||||
|
||||
if isControlling {
|
||||
realConn, err = conn.agent.Dial(context.TODO(), credentials.uFrag, credentials.pwd)
|
||||
} else {
|
||||
realConn, err = conn.agent.Accept(context.TODO(), credentials.uFrag, credentials.pwd)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return realConn, err
|
||||
}
|
||||
|
||||
// signalCredentials prepares local user credentials and signals them to the remote peer
|
||||
func (conn *Connection) signalCredentials() error {
|
||||
localUFrag, localPwd, err := conn.agent.GetLocalUserCredentials()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.signalOffer(localUFrag, localPwd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// listenOnLocalCandidates registers callback of an ICE Agent to receive new local connection candidates and then
|
||||
// signals them to the remote peer
|
||||
func (conn *Connection) listenOnLocalCandidates() error {
|
||||
err := conn.agent.OnCandidate(func(candidate ice.Candidate) {
|
||||
if candidate != nil {
|
||||
log.Debugf("discovered local candidate %s", candidate.String())
|
||||
err := conn.signalCandidate(candidate)
|
||||
if err != nil {
|
||||
log.Errorf("failed signaling candidate to the remote peer %s %s", conn.Config.RemoteWgKey.String(), err)
|
||||
//todo ??
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// listenOnConnectionStateChanges registers callback of an ICE Agent to track connection state
|
||||
func (conn *Connection) listenOnConnectionStateChanges() error {
|
||||
err := conn.agent.OnConnectionStateChange(func(state ice.ConnectionState) {
|
||||
log.Debugf("ICE Connection State has changed for peer %s -> %s", conn.Config.RemoteWgKey.String(), state.String())
|
||||
if state == ice.ConnectionStateConnected {
|
||||
// closed the connection has been established we can check the selected candidate pair
|
||||
pair, err := conn.agent.GetSelectedCandidatePair()
|
||||
if err != nil {
|
||||
log.Errorf("failed selecting active ICE candidate pair %s", err)
|
||||
return
|
||||
}
|
||||
log.Debugf("ICE connected to peer %s via a selected connnection candidate pair %s", conn.Config.RemoteWgKey.String(), pair)
|
||||
} else if state == ice.ConnectionStateDisconnected || state == ice.ConnectionStateFailed {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
log.Warnf("error while closing connection to peer %s -> %s", conn.Config.RemoteWgKey.String(), err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -3,60 +3,90 @@ package internal
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
ice "github.com/pion/ice/v2"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal/peer"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal/proxy"
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
||||
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
signal "github.com/wiretrustee/wiretrustee/signal/client"
|
||||
sProto "github.com/wiretrustee/wiretrustee/signal/proto"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PeerConnectionTimeout is a timeout of an initial connection attempt to a remote peer.
|
||||
// E.g. this peer will wait PeerConnectionTimeout for the remote peer to respond, if not successful then it will retry the connection attempt.
|
||||
const PeerConnectionTimeout = 40 * time.Second
|
||||
// PeerConnectionTimeoutMax is a timeout of an initial connection attempt to a remote peer.
|
||||
// E.g. this peer will wait PeerConnectionTimeoutMax for the remote peer to respond,
|
||||
// if not successful then it will retry the connection attempt.
|
||||
// Todo pass timeout at EnginConfig
|
||||
const (
|
||||
PeerConnectionTimeoutMax = 45000 // ms
|
||||
PeerConnectionTimeoutMin = 30000 // ms
|
||||
)
|
||||
|
||||
const WgPort = 51820
|
||||
|
||||
// EngineConfig is a config for the Engine
|
||||
type EngineConfig struct {
|
||||
WgIface string
|
||||
WgPort int
|
||||
WgIfaceName string
|
||||
|
||||
// WgAddr is a Wireguard local address (Wiretrustee Network IP)
|
||||
WgAddr string
|
||||
|
||||
// WgPrivateKey is a Wireguard private key of our peer (it MUST never leave the machine)
|
||||
WgPrivateKey wgtypes.Key
|
||||
|
||||
// IFaceBlackList is a list of network interfaces to ignore when discovering connection candidates (ICE related)
|
||||
IFaceBlackList map[string]struct{}
|
||||
|
||||
PreSharedKey *wgtypes.Key
|
||||
|
||||
// UDPMuxPort default value 0 - the system will pick an available port
|
||||
UDPMuxPort int
|
||||
|
||||
// UDPMuxSrflxPort default value 0 - the system will pick an available port
|
||||
UDPMuxSrflxPort int
|
||||
}
|
||||
|
||||
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
|
||||
type Engine struct {
|
||||
// signal is a Signal Service client
|
||||
signal *signal.Client
|
||||
signal signal.Client
|
||||
// mgmClient is a Management Service client
|
||||
mgmClient *mgm.Client
|
||||
// conns is a collection of remote peer connections indexed by local public key of the remote peers
|
||||
conns map[string]*Connection
|
||||
mgmClient mgm.Client
|
||||
// peerConns is a map that holds all the peers that are known to this peer
|
||||
peerConns map[string]*peer.Conn
|
||||
|
||||
// peerMux is used to sync peer operations (e.g. open connection, peer removal)
|
||||
peerMux *sync.Mutex
|
||||
// syncMsgMux is used to guarantee sequential Management Service message processing
|
||||
syncMsgMux *sync.Mutex
|
||||
|
||||
config *EngineConfig
|
||||
|
||||
// wgPort is a Wireguard local listen port
|
||||
wgPort int
|
||||
|
||||
// STUNs is a list of STUN servers used by ICE
|
||||
STUNs []*ice.URL
|
||||
// TURNs is a list of STUN servers used by ICE
|
||||
TURNs []*ice.URL
|
||||
|
||||
cancel context.CancelFunc
|
||||
|
||||
ctx context.Context
|
||||
|
||||
wgInterface iface.WGIface
|
||||
|
||||
udpMux ice.UDPMux
|
||||
udpMuxSrflx ice.UniversalUDPMux
|
||||
udpMuxConn *net.UDPConn
|
||||
udpMuxConnSrflx *net.UDPConn
|
||||
|
||||
// networkSerial is the latest Serial (state ID) of the network sent by the Management service
|
||||
networkSerial uint64
|
||||
}
|
||||
|
||||
// Peer is an instance of the Connection Peer
|
||||
@@ -66,28 +96,68 @@ type Peer struct {
|
||||
}
|
||||
|
||||
// NewEngine creates a new Connection Engine
|
||||
func NewEngine(signalClient *signal.Client, mgmClient *mgm.Client, config *EngineConfig, cancel context.CancelFunc) *Engine {
|
||||
func NewEngine(
|
||||
signalClient signal.Client, mgmClient mgm.Client, config *EngineConfig,
|
||||
cancel context.CancelFunc, ctx context.Context,
|
||||
) *Engine {
|
||||
return &Engine{
|
||||
signal: signalClient,
|
||||
mgmClient: mgmClient,
|
||||
conns: map[string]*Connection{},
|
||||
peerMux: &sync.Mutex{},
|
||||
syncMsgMux: &sync.Mutex{},
|
||||
config: config,
|
||||
STUNs: []*ice.URL{},
|
||||
TURNs: []*ice.URL{},
|
||||
cancel: cancel,
|
||||
signal: signalClient,
|
||||
mgmClient: mgmClient,
|
||||
peerConns: map[string]*peer.Conn{},
|
||||
syncMsgMux: &sync.Mutex{},
|
||||
config: config,
|
||||
STUNs: []*ice.URL{},
|
||||
TURNs: []*ice.URL{},
|
||||
cancel: cancel,
|
||||
ctx: ctx,
|
||||
networkSerial: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Engine) Stop() error {
|
||||
log.Debugf("removing Wiretrustee interface %s", e.config.WgIface)
|
||||
err := iface.Close()
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
err := e.removeAllPeers()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Wiretrustee interface %s %v", e.config.WgIface, err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("removing Wiretrustee interface %s", e.config.WgIfaceName)
|
||||
if e.wgInterface.Interface != nil {
|
||||
err = e.wgInterface.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Wiretrustee interface %s %v", e.config.WgIfaceName, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if e.udpMux != nil {
|
||||
if err := e.udpMux.Close(); err != nil {
|
||||
log.Debugf("close udp mux: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if e.udpMuxSrflx != nil {
|
||||
if err := e.udpMuxSrflx.Close(); err != nil {
|
||||
log.Debugf("close server reflexive udp mux: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if e.udpMuxConn != nil {
|
||||
if err := e.udpMuxConn.Close(); err != nil {
|
||||
log.Debugf("close udp mux connection: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if e.udpMuxConnSrflx != nil {
|
||||
if err := e.udpMuxConnSrflx.Close(); err != nil {
|
||||
log.Debugf("close server reflexive udp mux connection: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("stopped Wiretrustee Engine")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -95,29 +165,46 @@ func (e *Engine) Stop() error {
|
||||
// Connections to remote peers are not established here.
|
||||
// However, they will be established once an event with a list of peers to connect to will be received from Management Service
|
||||
func (e *Engine) Start() error {
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
wgIface := e.config.WgIface
|
||||
wgIfaceName := e.config.WgIfaceName
|
||||
wgAddr := e.config.WgAddr
|
||||
myPrivateKey := e.config.WgPrivateKey
|
||||
var err error
|
||||
|
||||
err := iface.Create(wgIface, wgAddr)
|
||||
e.wgInterface, err = iface.NewWGIface(wgIfaceName, wgAddr, iface.DefaultMTU)
|
||||
if err != nil {
|
||||
log.Errorf("failed creating interface %s: [%s]", wgIface, err.Error())
|
||||
log.Errorf("failed creating wireguard interface instance %s: [%s]", wgIfaceName, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = iface.Configure(wgIface, myPrivateKey.String())
|
||||
e.udpMuxConn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: e.config.UDPMuxPort})
|
||||
if err != nil {
|
||||
log.Errorf("failed configuring Wireguard interface [%s]: %s", wgIface, err.Error())
|
||||
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxPort, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
port, err := iface.GetListenPort(wgIface)
|
||||
e.udpMuxConnSrflx, err = net.ListenUDP("udp4", &net.UDPAddr{Port: e.config.UDPMuxSrflxPort})
|
||||
if err != nil {
|
||||
log.Errorf("failed getting Wireguard listen port [%s]: %s", wgIface, err.Error())
|
||||
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxSrflxPort, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
e.udpMux = ice.NewUDPMuxDefault(ice.UDPMuxParams{UDPConn: e.udpMuxConn})
|
||||
e.udpMuxSrflx = ice.NewUniversalUDPMuxDefault(ice.UniversalUDPMuxParams{UDPConn: e.udpMuxConnSrflx})
|
||||
|
||||
err = e.wgInterface.Create()
|
||||
if err != nil {
|
||||
log.Errorf("failed creating tunnel interface %s: [%s]", wgIfaceName, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.wgInterface.Configure(myPrivateKey.String(), e.config.WgPort)
|
||||
if err != nil {
|
||||
log.Errorf("failed configuring Wireguard interface [%s]: %s", wgIfaceName, err.Error())
|
||||
return err
|
||||
}
|
||||
e.wgPort = *port
|
||||
|
||||
e.receiveSignalEvents()
|
||||
e.receiveManagementEvents()
|
||||
@@ -125,46 +212,34 @@ func (e *Engine) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// initializePeer peer agent attempt to open connection
|
||||
func (e *Engine) initializePeer(peer Peer) {
|
||||
var backOff = &backoff.ExponentialBackOff{
|
||||
InitialInterval: backoff.DefaultInitialInterval,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 5 * time.Second,
|
||||
MaxElapsedTime: time.Duration(0), //never stop
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
// removePeers finds and removes peers that do not exist anymore in the network map received from the Management Service
|
||||
func (e *Engine) removePeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
|
||||
currentPeers := make([]string, 0, len(e.peerConns))
|
||||
for p := range e.peerConns {
|
||||
currentPeers = append(currentPeers, p)
|
||||
}
|
||||
operation := func() error {
|
||||
_, err := e.openPeerConnection(e.wgPort, e.config.WgPrivateKey, peer)
|
||||
e.peerMux.Lock()
|
||||
defer e.peerMux.Unlock()
|
||||
if _, ok := e.conns[peer.WgPubKey]; !ok {
|
||||
log.Infof("removing connection attempt with Peer: %v, not retrying", peer.WgPubKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
newPeers := make([]string, 0, len(peersUpdate))
|
||||
for _, p := range peersUpdate {
|
||||
newPeers = append(newPeers, p.GetWgPubKey())
|
||||
}
|
||||
|
||||
toRemove := util.SliceDiff(currentPeers, newPeers)
|
||||
|
||||
for _, p := range toRemove {
|
||||
err := e.removePeer(p)
|
||||
if err != nil {
|
||||
log.Warnln(err)
|
||||
log.Warnln("retrying connection because of error: ", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, backOff)
|
||||
if err != nil {
|
||||
// should actually never happen
|
||||
panic(err)
|
||||
log.Infof("removed peer %s", p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) removePeerConnections(peers []string) error {
|
||||
e.peerMux.Lock()
|
||||
defer e.peerMux.Unlock()
|
||||
for _, peer := range peers {
|
||||
err := e.removePeerConnection(peer)
|
||||
func (e *Engine) removeAllPeers() error {
|
||||
log.Debugf("removing all peer connections")
|
||||
for p := range e.peerConns {
|
||||
err := e.removePeer(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -172,68 +247,62 @@ func (e *Engine) removePeerConnections(peers []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// removePeerConnection closes existing peer connection and removes peer
|
||||
func (e *Engine) removePeerConnection(peerKey string) error {
|
||||
conn, exists := e.conns[peerKey]
|
||||
if exists && conn != nil {
|
||||
delete(e.conns, peerKey)
|
||||
return conn.Close()
|
||||
// removePeer closes an existing peer connection and removes a peer
|
||||
func (e *Engine) removePeer(peerKey string) error {
|
||||
log.Debugf("removing peer from engine %s", peerKey)
|
||||
conn, exists := e.peerConns[peerKey]
|
||||
if exists {
|
||||
delete(e.peerConns, peerKey)
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *peer.ConnectionAlreadyClosedError:
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPeerConnectionStatus returns a connection Status or nil if peer connection wasn't found
|
||||
func (e *Engine) GetPeerConnectionStatus(peerKey string) *Status {
|
||||
e.peerMux.Lock()
|
||||
defer e.peerMux.Unlock()
|
||||
|
||||
conn, exists := e.conns[peerKey]
|
||||
func (e *Engine) GetPeerConnectionStatus(peerKey string) peer.ConnStatus {
|
||||
conn, exists := e.peerConns[peerKey]
|
||||
if exists && conn != nil {
|
||||
return &conn.Status
|
||||
return conn.Status()
|
||||
}
|
||||
|
||||
return nil
|
||||
return -1
|
||||
}
|
||||
|
||||
// openPeerConnection opens a new remote peer connection
|
||||
func (e *Engine) openPeerConnection(wgPort int, myKey wgtypes.Key, peer Peer) (*Connection, error) {
|
||||
e.peerMux.Lock()
|
||||
func (e *Engine) GetPeers() []string {
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
remoteKey, _ := wgtypes.ParseKey(peer.WgPubKey)
|
||||
connConfig := &ConnConfig{
|
||||
WgListenAddr: fmt.Sprintf("127.0.0.1:%d", wgPort),
|
||||
WgPeerIP: e.config.WgAddr,
|
||||
WgIface: e.config.WgIface,
|
||||
WgAllowedIPs: peer.WgAllowedIps,
|
||||
WgKey: myKey,
|
||||
RemoteWgKey: remoteKey,
|
||||
StunTurnURLS: append(e.STUNs, e.TURNs...),
|
||||
iFaceBlackList: e.config.IFaceBlackList,
|
||||
peers := []string{}
|
||||
for s := range e.peerConns {
|
||||
peers = append(peers, s)
|
||||
}
|
||||
|
||||
signalOffer := func(uFrag string, pwd string) error {
|
||||
return signalAuth(uFrag, pwd, myKey, remoteKey, e.signal, false)
|
||||
}
|
||||
|
||||
signalAnswer := func(uFrag string, pwd string) error {
|
||||
return signalAuth(uFrag, pwd, myKey, remoteKey, e.signal, true)
|
||||
}
|
||||
signalCandidate := func(candidate ice.Candidate) error {
|
||||
return signalCandidate(candidate, myKey, remoteKey, e.signal)
|
||||
}
|
||||
conn := NewConnection(*connConfig, signalCandidate, signalOffer, signalAnswer)
|
||||
e.conns[remoteKey.String()] = conn
|
||||
e.peerMux.Unlock()
|
||||
|
||||
// blocks until the connection is open (or timeout)
|
||||
err := conn.Open(PeerConnectionTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
return peers
|
||||
}
|
||||
|
||||
func signalCandidate(candidate ice.Candidate, myKey wgtypes.Key, remoteKey wgtypes.Key, s *signal.Client) error {
|
||||
// GetConnectedPeers returns a connection Status or nil if peer connection wasn't found
|
||||
func (e *Engine) GetConnectedPeers() []string {
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
peers := []string{}
|
||||
for s, conn := range e.peerConns {
|
||||
if conn.Status() == peer.StatusConnected {
|
||||
peers = append(peers, s)
|
||||
}
|
||||
}
|
||||
|
||||
return peers
|
||||
}
|
||||
|
||||
func signalCandidate(candidate ice.Candidate, myKey wgtypes.Key, remoteKey wgtypes.Key, s signal.Client) error {
|
||||
err := s.Send(&sProto.Message{
|
||||
Key: myKey.PublicKey().String(),
|
||||
RemoteKey: remoteKey.String(),
|
||||
@@ -244,15 +313,14 @@ func signalCandidate(candidate ice.Candidate, myKey wgtypes.Key, remoteKey wgtyp
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed signaling candidate to the remote peer %s %s", remoteKey.String(), err)
|
||||
//todo ??
|
||||
// todo ??
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func signalAuth(uFrag string, pwd string, myKey wgtypes.Key, remoteKey wgtypes.Key, s *signal.Client, isAnswer bool) error {
|
||||
|
||||
func signalAuth(uFrag string, pwd string, myKey wgtypes.Key, remoteKey wgtypes.Key, s signal.Client, isAnswer bool) error {
|
||||
var t sProto.Body_Type
|
||||
if isAnswer {
|
||||
t = sProto.Body_ANSWER
|
||||
@@ -262,7 +330,8 @@ func signalAuth(uFrag string, pwd string, myKey wgtypes.Key, remoteKey wgtypes.K
|
||||
|
||||
msg, err := signal.MarshalCredential(myKey, remoteKey, &signal.Credential{
|
||||
UFrag: uFrag,
|
||||
Pwd: pwd}, t)
|
||||
Pwd: pwd,
|
||||
}, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -274,43 +343,49 @@ func signalAuth(uFrag string, pwd string, myKey wgtypes.Key, remoteKey wgtypes.K
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
if update.GetWiretrusteeConfig() != nil {
|
||||
err := e.updateTURNs(update.GetWiretrusteeConfig().GetTurns())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.updateSTUNs(update.GetWiretrusteeConfig().GetStuns())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// todo update signal
|
||||
}
|
||||
|
||||
if update.GetNetworkMap() != nil {
|
||||
// only apply new changes and ignore old ones
|
||||
err := e.updateNetworkMap(update.GetNetworkMap())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// receiveManagementEvents connects to the Management Service event stream to receive updates from the management service
|
||||
// E.g. when a new peer has been registered and we are allowed to connect to it.
|
||||
func (e *Engine) receiveManagementEvents() {
|
||||
go func() {
|
||||
err := e.mgmClient.Sync(func(update *mgmProto.SyncResponse) error {
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
if update.GetWiretrusteeConfig() != nil {
|
||||
err := e.updateTURNs(update.GetWiretrusteeConfig().GetTurns())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.updateSTUNs(update.GetWiretrusteeConfig().GetStuns())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//todo update signal
|
||||
}
|
||||
|
||||
if update.GetRemotePeers() != nil || update.GetRemotePeersIsEmpty() {
|
||||
// empty arrays are serialized by protobuf to null, but for our case empty array is a valid state.
|
||||
err := e.updatePeers(update.GetRemotePeers())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return e.handleSync(update)
|
||||
})
|
||||
if err != nil {
|
||||
// happens if management is unavailable for a long time.
|
||||
// We want to cancel the operation of the whole client
|
||||
e.cancel()
|
||||
return
|
||||
}
|
||||
log.Infof("connected to Management Service updates stream")
|
||||
log.Debugf("stopped receiving updates from Management Service")
|
||||
}()
|
||||
log.Debugf("connecting to Management Service updates stream")
|
||||
}
|
||||
@@ -353,104 +428,199 @@ func (e *Engine) updateTURNs(turns []*mgmProto.ProtectedHostConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) updatePeers(remotePeers []*mgmProto.RemotePeerConfig) error {
|
||||
log.Debugf("got peers update from Management Service, updating")
|
||||
remotePeerMap := make(map[string]struct{})
|
||||
for _, peer := range remotePeers {
|
||||
remotePeerMap[peer.GetWgPubKey()] = struct{}{}
|
||||
func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
||||
serial := networkMap.GetSerial()
|
||||
if e.networkSerial > serial {
|
||||
log.Debugf("received outdated NetworkMap with serial %d, ignoring", serial)
|
||||
return nil
|
||||
}
|
||||
|
||||
//remove peers that are no longer available for us
|
||||
toRemove := []string{}
|
||||
for p := range e.conns {
|
||||
if _, ok := remotePeerMap[p]; !ok {
|
||||
toRemove = append(toRemove, p)
|
||||
log.Debugf("got peers update from Management Service, total peers to connect to = %d", len(networkMap.GetRemotePeers()))
|
||||
|
||||
// cleanup request, most likely our peer has been deleted
|
||||
if networkMap.GetRemotePeersIsEmpty() {
|
||||
err := e.removeAllPeers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := e.removePeers(networkMap.GetRemotePeers())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.addNewPeers(networkMap.GetRemotePeers())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := e.removePeerConnections(toRemove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add new peers
|
||||
for _, peer := range remotePeers {
|
||||
peerKey := peer.GetWgPubKey()
|
||||
peerIPs := peer.GetAllowedIps()
|
||||
if _, ok := e.conns[peerKey]; !ok {
|
||||
go e.initializePeer(Peer{
|
||||
WgPubKey: peerKey,
|
||||
WgAllowedIps: strings.Join(peerIPs, ","),
|
||||
})
|
||||
e.networkSerial = serial
|
||||
return nil
|
||||
}
|
||||
|
||||
// addNewPeers finds and adds peers that were not know before but arrived from the Management service with the update
|
||||
func (e *Engine) addNewPeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
|
||||
for _, p := range peersUpdate {
|
||||
peerKey := p.GetWgPubKey()
|
||||
peerIPs := p.GetAllowedIps()
|
||||
if _, ok := e.peerConns[peerKey]; !ok {
|
||||
conn, err := e.createPeerConn(peerKey, strings.Join(peerIPs, ","))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.peerConns[peerKey] = conn
|
||||
|
||||
go e.connWorker(conn, peerKey)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Engine) connWorker(conn *peer.Conn, peerKey string) {
|
||||
for {
|
||||
|
||||
// randomize starting time a bit
|
||||
min := 500
|
||||
max := 2000
|
||||
time.Sleep(time.Duration(rand.Intn(max-min)+min) * time.Millisecond)
|
||||
|
||||
// if peer has been removed -> give up
|
||||
if !e.peerExists(peerKey) {
|
||||
log.Infof("peer %s doesn't exist anymore, won't retry connection", peerKey)
|
||||
return
|
||||
}
|
||||
|
||||
if !e.signal.Ready() {
|
||||
log.Infof("signal client isn't ready, skipping connection attempt %s", peerKey)
|
||||
continue
|
||||
}
|
||||
|
||||
err := conn.Open()
|
||||
if err != nil {
|
||||
log.Debugf("connection to peer %s failed: %v", peerKey, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e Engine) peerExists(peerKey string) bool {
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
_, ok := e.peerConns[peerKey]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, error) {
|
||||
var stunTurn []*ice.URL
|
||||
stunTurn = append(stunTurn, e.STUNs...)
|
||||
stunTurn = append(stunTurn, e.TURNs...)
|
||||
|
||||
interfaceBlacklist := make([]string, 0, len(e.config.IFaceBlackList))
|
||||
for k := range e.config.IFaceBlackList {
|
||||
interfaceBlacklist = append(interfaceBlacklist, k)
|
||||
}
|
||||
|
||||
proxyConfig := proxy.Config{
|
||||
RemoteKey: pubKey,
|
||||
WgListenAddr: fmt.Sprintf("127.0.0.1:%d", e.config.WgPort),
|
||||
WgInterface: e.wgInterface,
|
||||
AllowedIps: allowedIPs,
|
||||
PreSharedKey: e.config.PreSharedKey,
|
||||
}
|
||||
|
||||
// randomize connection timeout
|
||||
timeout := time.Duration(rand.Intn(PeerConnectionTimeoutMax-PeerConnectionTimeoutMin)+PeerConnectionTimeoutMin) * time.Millisecond
|
||||
config := peer.ConnConfig{
|
||||
Key: pubKey,
|
||||
LocalKey: e.config.WgPrivateKey.PublicKey().String(),
|
||||
StunTurn: stunTurn,
|
||||
InterfaceBlackList: interfaceBlacklist,
|
||||
Timeout: timeout,
|
||||
UDPMux: e.udpMux,
|
||||
UDPMuxSrflx: e.udpMuxSrflx,
|
||||
ProxyConfig: proxyConfig,
|
||||
}
|
||||
|
||||
peerConn, err := peer.NewConn(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wgPubKey, err := wgtypes.ParseKey(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signalOffer := func(uFrag string, pwd string) error {
|
||||
return signalAuth(uFrag, pwd, e.config.WgPrivateKey, wgPubKey, e.signal, false)
|
||||
}
|
||||
|
||||
signalCandidate := func(candidate ice.Candidate) error {
|
||||
return signalCandidate(candidate, e.config.WgPrivateKey, wgPubKey, e.signal)
|
||||
}
|
||||
|
||||
signalAnswer := func(uFrag string, pwd string) error {
|
||||
return signalAuth(uFrag, pwd, e.config.WgPrivateKey, wgPubKey, e.signal, true)
|
||||
}
|
||||
|
||||
peerConn.SetSignalCandidate(signalCandidate)
|
||||
peerConn.SetSignalOffer(signalOffer)
|
||||
peerConn.SetSignalAnswer(signalAnswer)
|
||||
|
||||
return peerConn, nil
|
||||
}
|
||||
|
||||
// receiveSignalEvents connects to the Signal Service event stream to negotiate connection with remote peers
|
||||
func (e *Engine) receiveSignalEvents() {
|
||||
// connect to a stream of messages coming from the signal server
|
||||
e.signal.Receive(func(msg *sProto.Message) error {
|
||||
go func() {
|
||||
// connect to a stream of messages coming from the signal server
|
||||
err := e.signal.Receive(func(msg *sProto.Message) error {
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
conn := e.conns[msg.Key]
|
||||
if conn == nil {
|
||||
return fmt.Errorf("wrongly addressed message %s", msg.Key)
|
||||
}
|
||||
|
||||
if conn.Config.RemoteWgKey.String() != msg.Key {
|
||||
return fmt.Errorf("unknown peer %s", msg.Key)
|
||||
}
|
||||
|
||||
switch msg.GetBody().Type {
|
||||
case sProto.Body_OFFER:
|
||||
remoteCred, err := signal.UnMarshalCredential(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
conn := e.peerConns[msg.Key]
|
||||
if conn == nil {
|
||||
return fmt.Errorf("wrongly addressed message %s", msg.Key)
|
||||
}
|
||||
err = conn.OnOffer(IceCredentials{
|
||||
uFrag: remoteCred.UFrag,
|
||||
pwd: remoteCred.Pwd,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
switch msg.GetBody().Type {
|
||||
case sProto.Body_OFFER:
|
||||
remoteCred, err := signal.UnMarshalCredential(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.OnRemoteOffer(peer.IceCredentials{
|
||||
UFrag: remoteCred.UFrag,
|
||||
Pwd: remoteCred.Pwd,
|
||||
})
|
||||
case sProto.Body_ANSWER:
|
||||
remoteCred, err := signal.UnMarshalCredential(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.OnRemoteAnswer(peer.IceCredentials{
|
||||
UFrag: remoteCred.UFrag,
|
||||
Pwd: remoteCred.Pwd,
|
||||
})
|
||||
case sProto.Body_CANDIDATE:
|
||||
candidate, err := ice.UnmarshalCandidate(msg.GetBody().Payload)
|
||||
if err != nil {
|
||||
log.Errorf("failed on parsing remote candidate %s -> %s", candidate, err)
|
||||
return err
|
||||
}
|
||||
conn.OnRemoteCandidate(candidate)
|
||||
}
|
||||
|
||||
return nil
|
||||
case sProto.Body_ANSWER:
|
||||
remoteCred, err := signal.UnMarshalCredential(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = conn.OnAnswer(IceCredentials{
|
||||
uFrag: remoteCred.UFrag,
|
||||
pwd: remoteCred.Pwd,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case sProto.Body_CANDIDATE:
|
||||
|
||||
candidate, err := ice.UnmarshalCandidate(msg.GetBody().Payload)
|
||||
if err != nil {
|
||||
log.Errorf("failed on parsing remote candidate %s -> %s", candidate, err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.OnRemoteCandidate(candidate)
|
||||
if err != nil {
|
||||
log.Errorf("error handling CANDIATE from %s", msg.Key)
|
||||
return err
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
// happens if signal is unavailable for a long time.
|
||||
// We want to cancel the operation of the whole client
|
||||
e.cancel()
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
e.signal.WaitConnected()
|
||||
e.signal.WaitStreamConnected()
|
||||
}
|
||||
|
||||
468
client/internal/engine_test.go
Normal file
@@ -0,0 +1,468 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/client/system"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/client"
|
||||
mgmtProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
"github.com/wiretrustee/wiretrustee/management/server"
|
||||
signal "github.com/wiretrustee/wiretrustee/signal/client"
|
||||
"github.com/wiretrustee/wiretrustee/signal/proto"
|
||||
signalServer "github.com/wiretrustee/wiretrustee/signal/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
var (
|
||||
kaep = keepalive.EnforcementPolicy{
|
||||
MinTime: 15 * time.Second,
|
||||
PermitWithoutStream: true,
|
||||
}
|
||||
|
||||
kasp = keepalive.ServerParameters{
|
||||
MaxConnectionIdle: 15 * time.Second,
|
||||
MaxConnectionAgeGrace: 5 * time.Second,
|
||||
Time: 5 * time.Second,
|
||||
Timeout: 2 * time.Second,
|
||||
}
|
||||
)
|
||||
|
||||
func TestEngine_UpdateNetworkMap(t *testing.T) {
|
||||
|
||||
// test setup
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
engine := NewEngine(&signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||
WgIfaceName: "utun100",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
}, cancel, ctx)
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
networkMap *mgmtProto.NetworkMap
|
||||
|
||||
expectedLen int
|
||||
expectedPeers []string
|
||||
expectedSerial uint64
|
||||
}
|
||||
|
||||
peer1 := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "RRHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
AllowedIps: []string{"100.64.0.10/24"},
|
||||
}
|
||||
|
||||
peer2 := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "LLHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
AllowedIps: []string{"100.64.0.11/24"},
|
||||
}
|
||||
|
||||
peer3 := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "GGHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
AllowedIps: []string{"100.64.0.12/24"},
|
||||
}
|
||||
|
||||
case1 := testCase{
|
||||
name: "input with a new peer to add",
|
||||
networkMap: &mgmtProto.NetworkMap{
|
||||
Serial: 1,
|
||||
PeerConfig: nil,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{
|
||||
peer1,
|
||||
},
|
||||
RemotePeersIsEmpty: false,
|
||||
},
|
||||
expectedLen: 1,
|
||||
expectedPeers: []string{peer1.GetWgPubKey()},
|
||||
expectedSerial: 1,
|
||||
}
|
||||
|
||||
// 2nd case - one extra peer added and network map has Serial grater than local => apply the update
|
||||
case2 := testCase{
|
||||
name: "input with an old peer and a new peer to add",
|
||||
networkMap: &mgmtProto.NetworkMap{
|
||||
Serial: 2,
|
||||
PeerConfig: nil,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{
|
||||
peer1, peer2,
|
||||
},
|
||||
RemotePeersIsEmpty: false,
|
||||
},
|
||||
expectedLen: 2,
|
||||
expectedPeers: []string{peer1.GetWgPubKey(), peer2.GetWgPubKey()},
|
||||
expectedSerial: 2,
|
||||
}
|
||||
|
||||
case3 := testCase{
|
||||
name: "input with outdated (old) update to ignore",
|
||||
networkMap: &mgmtProto.NetworkMap{
|
||||
Serial: 0,
|
||||
PeerConfig: nil,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{
|
||||
peer1, peer2, peer3,
|
||||
},
|
||||
RemotePeersIsEmpty: false,
|
||||
},
|
||||
expectedLen: 2,
|
||||
expectedPeers: []string{peer1.GetWgPubKey(), peer2.GetWgPubKey()},
|
||||
expectedSerial: 2,
|
||||
}
|
||||
|
||||
case4 := testCase{
|
||||
name: "input with one peer to remove and one new to add",
|
||||
networkMap: &mgmtProto.NetworkMap{
|
||||
Serial: 4,
|
||||
PeerConfig: nil,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{
|
||||
peer2, peer3,
|
||||
},
|
||||
RemotePeersIsEmpty: false,
|
||||
},
|
||||
expectedLen: 2,
|
||||
expectedPeers: []string{peer2.GetWgPubKey(), peer3.GetWgPubKey()},
|
||||
expectedSerial: 4,
|
||||
}
|
||||
|
||||
case5 := testCase{
|
||||
name: "input with all peers to remove",
|
||||
networkMap: &mgmtProto.NetworkMap{
|
||||
Serial: 5,
|
||||
PeerConfig: nil,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{},
|
||||
RemotePeersIsEmpty: true,
|
||||
},
|
||||
expectedLen: 0,
|
||||
expectedPeers: nil,
|
||||
expectedSerial: 5,
|
||||
}
|
||||
|
||||
for _, c := range []testCase{case1, case2, case3, case4, case5} {
|
||||
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
err = engine.updateNetworkMap(c.networkMap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(engine.peerConns) != c.expectedLen {
|
||||
t.Errorf("expecting Engine.peerConns to be of size %d, got %d", c.expectedLen, len(engine.peerConns))
|
||||
}
|
||||
|
||||
if engine.networkSerial != c.expectedSerial {
|
||||
t.Errorf("expecting Engine.networkSerial to be equal to %d, actual %d", c.expectedSerial, engine.networkSerial)
|
||||
}
|
||||
|
||||
for _, p := range c.expectedPeers {
|
||||
if _, ok := engine.peerConns[p]; !ok {
|
||||
t.Errorf("expecting Engine.peerConns to contain peer %s", p)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEngine_Sync(t *testing.T) {
|
||||
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// feed updates to Engine via mocked Management client
|
||||
updates := make(chan *mgmtProto.SyncResponse)
|
||||
defer close(updates)
|
||||
syncFunc := func(msgHandler func(msg *mgmtProto.SyncResponse) error) error {
|
||||
|
||||
for msg := range updates {
|
||||
err := msgHandler(msg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
engine := NewEngine(&signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, &EngineConfig{
|
||||
WgIfaceName: "utun100",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
}, cancel, ctx)
|
||||
|
||||
defer func() {
|
||||
err := engine.Stop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
err = engine.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
peer1 := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "RRHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
AllowedIps: []string{"100.64.0.10/24"},
|
||||
}
|
||||
peer2 := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "LLHf3Ma6z6mdLbriAJbqhX9+nM/B71lgw2+91q3LlhU=",
|
||||
AllowedIps: []string{"100.64.0.11/24"},
|
||||
}
|
||||
peer3 := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "GGHf3Ma6z6mdLbriAJbqhX9+nM/B71lgw2+91q3LlhU=",
|
||||
AllowedIps: []string{"100.64.0.12/24"},
|
||||
}
|
||||
// 1st update with just 1 peer and serial larger than the current serial of the engine => apply update
|
||||
updates <- &mgmtProto.SyncResponse{
|
||||
NetworkMap: &mgmtProto.NetworkMap{
|
||||
Serial: 10,
|
||||
PeerConfig: nil,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{peer1, peer2, peer3},
|
||||
RemotePeersIsEmpty: false,
|
||||
},
|
||||
}
|
||||
|
||||
timeout := time.After(time.Second * 2)
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatalf("timeout while waiting for test to finish")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if len(engine.GetPeers()) == 3 && engine.networkSerial == 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEngine_MultiplePeers(t *testing.T) {
|
||||
|
||||
//log.SetLevel(log.DebugLevel)
|
||||
|
||||
dir := t.TempDir()
|
||||
|
||||
err := util.CopyFileContents("../testdata/store.json", filepath.Join(dir, "store.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = os.Remove(filepath.Join(dir, "store.json")) //nolint
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
sport := 10010
|
||||
sigServer, err := startSignal(sport)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer sigServer.Stop()
|
||||
mport := 33081
|
||||
mgmtServer, err := startManagement(mport, dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer mgmtServer.GracefulStop()
|
||||
|
||||
setupKey := "A2C8E62B-38F5-4553-B31E-DD66C696CEBB"
|
||||
|
||||
mu := sync.Mutex{}
|
||||
engines := []*Engine{}
|
||||
numPeers := 10
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(numPeers)
|
||||
// create and start peers
|
||||
for i := 0; i < numPeers; i++ {
|
||||
j := i
|
||||
go func() {
|
||||
engine, err := createEngine(ctx, cancel, setupKey, j, mport, sport)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
engine.Start() //nolint
|
||||
engines = append(engines, engine)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
// wait until all have been created and started
|
||||
wg.Wait()
|
||||
// check whether all the peer have expected peers connected
|
||||
|
||||
expectedConnected := numPeers * (numPeers - 1)
|
||||
// adjust according to timeouts
|
||||
timeout := 50 * time.Second
|
||||
timeoutChan := time.After(timeout)
|
||||
for {
|
||||
select {
|
||||
case <-timeoutChan:
|
||||
t.Fatalf("waiting for expected connections timeout after %s", timeout.String())
|
||||
return
|
||||
default:
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
totalConnected := 0
|
||||
for _, engine := range engines {
|
||||
totalConnected = totalConnected + len(engine.GetConnectedPeers())
|
||||
}
|
||||
if totalConnected == expectedConnected {
|
||||
log.Debugf("total connected=%d", totalConnected)
|
||||
break
|
||||
}
|
||||
log.Infof("total connected=%d", totalConnected)
|
||||
}
|
||||
|
||||
// cleanup test
|
||||
for _, peerEngine := range engines {
|
||||
errStop := peerEngine.mgmClient.Close()
|
||||
if errStop != nil {
|
||||
log.Infoln("got error trying to close management clients from engine: ", errStop)
|
||||
}
|
||||
errStop = peerEngine.Stop()
|
||||
if errStop != nil {
|
||||
log.Infoln("got error trying to close testing peers engine: ", errStop)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey string, i int, mport int, sport int) (*Engine, error) {
|
||||
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mgmtClient, err := mgmt.NewClient(ctx, fmt.Sprintf("localhost:%d", mport), key, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signalClient, err := signal.NewClient(ctx, fmt.Sprintf("localhost:%d", sport), key, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicKey, err := mgmtClient.GetServerPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := system.GetInfo()
|
||||
resp, err := mgmtClient.Register(*publicKey, setupKey, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ifaceName string
|
||||
if runtime.GOOS == "darwin" {
|
||||
ifaceName = fmt.Sprintf("utun1%d", i)
|
||||
} else {
|
||||
ifaceName = fmt.Sprintf("wt%d", i)
|
||||
}
|
||||
|
||||
wgPort := 33100 + i
|
||||
conf := &EngineConfig{
|
||||
WgIfaceName: ifaceName,
|
||||
WgAddr: resp.PeerConfig.Address,
|
||||
WgPrivateKey: key,
|
||||
WgPort: wgPort,
|
||||
}
|
||||
|
||||
return NewEngine(signalClient, mgmtClient, conf, cancel, ctx), nil
|
||||
}
|
||||
|
||||
func startSignal(port int) (*grpc.Server, error) {
|
||||
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
proto.RegisterSignalExchangeServer(s, signalServer.NewServer())
|
||||
|
||||
go func() {
|
||||
if err = s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func startManagement(port int, dataDir string) (*grpc.Server, error) {
|
||||
|
||||
config := &server.Config{
|
||||
Stuns: []*server.Host{},
|
||||
TURNConfig: &server.TURNConfig{},
|
||||
Signal: &server.Host{
|
||||
Proto: "http",
|
||||
URI: "localhost:10000",
|
||||
},
|
||||
Datadir: dataDir,
|
||||
HttpConfig: nil,
|
||||
}
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||
store, err := server.NewStore(config.Datadir)
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
||||
}
|
||||
peersUpdateManager := server.NewPeersUpdateManager()
|
||||
accountManager := server.NewManager(store, peersUpdateManager, nil)
|
||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mgmtProto.RegisterManagementServiceServer(s, mgmtServer)
|
||||
go func() {
|
||||
if err = s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
446
client/internal/peer/conn.go
Normal file
@@ -0,0 +1,446 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/ice/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal/proxy"
|
||||
)
|
||||
|
||||
// ConnConfig is a peer Connection configuration
|
||||
type ConnConfig struct {
|
||||
|
||||
// Key is a public key of a remote peer
|
||||
Key string
|
||||
// LocalKey is a public key of a local peer
|
||||
LocalKey string
|
||||
|
||||
// StunTurn is a list of STUN and TURN URLs
|
||||
StunTurn []*ice.URL
|
||||
|
||||
// InterfaceBlackList is a list of machine interfaces that should be filtered out by ICE Candidate gathering
|
||||
// (e.g. if eth0 is in the list, host candidate of this interface won't be used)
|
||||
InterfaceBlackList []string
|
||||
|
||||
Timeout time.Duration
|
||||
|
||||
ProxyConfig proxy.Config
|
||||
|
||||
UDPMux ice.UDPMux
|
||||
UDPMuxSrflx ice.UniversalUDPMux
|
||||
}
|
||||
|
||||
// IceCredentials ICE protocol credentials struct
|
||||
type IceCredentials struct {
|
||||
UFrag string
|
||||
Pwd string
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
config ConnConfig
|
||||
mu sync.Mutex
|
||||
|
||||
// signalCandidate is a handler function to signal remote peer about local connection candidate
|
||||
signalCandidate func(candidate ice.Candidate) error
|
||||
// signalOffer is a handler function to signal remote peer our connection offer (credentials)
|
||||
signalOffer func(uFrag string, pwd string) error
|
||||
signalAnswer func(uFrag string, pwd string) error
|
||||
|
||||
// remoteOffersCh is a channel used to wait for remote credentials to proceed with the connection
|
||||
remoteOffersCh chan IceCredentials
|
||||
// remoteAnswerCh is a channel used to wait for remote credentials answer (confirmation of our offer) to proceed with the connection
|
||||
remoteAnswerCh chan IceCredentials
|
||||
closeCh chan struct{}
|
||||
ctx context.Context
|
||||
notifyDisconnected context.CancelFunc
|
||||
|
||||
agent *ice.Agent
|
||||
status ConnStatus
|
||||
|
||||
proxy proxy.Proxy
|
||||
}
|
||||
|
||||
// NewConn creates a new not opened Conn to the remote peer.
|
||||
// To establish a connection run Conn.Open
|
||||
func NewConn(config ConnConfig) (*Conn, error) {
|
||||
return &Conn{
|
||||
config: config,
|
||||
mu: sync.Mutex{},
|
||||
status: StatusDisconnected,
|
||||
closeCh: make(chan struct{}),
|
||||
remoteOffersCh: make(chan IceCredentials),
|
||||
remoteAnswerCh: make(chan IceCredentials),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// interfaceFilter is a function passed to ICE Agent to filter out blacklisted interfaces
|
||||
func interfaceFilter(blackList []string) func(string) bool {
|
||||
var blackListMap map[string]struct{}
|
||||
if blackList != nil {
|
||||
blackListMap = make(map[string]struct{})
|
||||
for _, s := range blackList {
|
||||
blackListMap[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
return func(iFace string) bool {
|
||||
|
||||
_, ok := blackListMap[iFace]
|
||||
if ok {
|
||||
return false
|
||||
}
|
||||
// look for unlisted Wireguard interfaces
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
log.Debugf("trying to create a wgctrl client failed with: %v", err)
|
||||
}
|
||||
defer wg.Close()
|
||||
|
||||
_, err = wg.Device(iFace)
|
||||
return err != nil
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *Conn) reCreateAgent() error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
failedTimeout := 6 * time.Second
|
||||
var err error
|
||||
conn.agent, err = ice.NewAgent(&ice.AgentConfig{
|
||||
MulticastDNSMode: ice.MulticastDNSModeDisabled,
|
||||
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4},
|
||||
Urls: conn.config.StunTurn,
|
||||
CandidateTypes: []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay},
|
||||
FailedTimeout: &failedTimeout,
|
||||
InterfaceFilter: interfaceFilter(conn.config.InterfaceBlackList),
|
||||
UDPMux: conn.config.UDPMux,
|
||||
UDPMuxSrflx: conn.config.UDPMuxSrflx,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.agent.OnCandidate(conn.onICECandidate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.agent.OnConnectionStateChange(conn.onICEConnectionStateChange)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.agent.OnSelectedCandidatePairChange(conn.onICESelectedCandidatePair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open opens connection to the remote peer starting ICE candidate gathering process.
|
||||
// Blocks until connection has been closed or connection timeout.
|
||||
// ConnStatus will be set accordingly
|
||||
func (conn *Conn) Open() error {
|
||||
log.Debugf("trying to connect to peer %s", conn.config.Key)
|
||||
|
||||
defer func() {
|
||||
err := conn.cleanup()
|
||||
if err != nil {
|
||||
log.Errorf("error while cleaning up peer connection %s: %v", conn.config.Key, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
err := conn.reCreateAgent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.sendOffer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("connection offer sent to peer %s, waiting for the confirmation", conn.config.Key)
|
||||
|
||||
// Only continue once we got a connection confirmation from the remote peer.
|
||||
// The connection timeout could have happened before a confirmation received from the remote.
|
||||
// The connection could have also been closed externally (e.g. when we received an update from the management that peer shouldn't be connected)
|
||||
var remoteCredentials IceCredentials
|
||||
select {
|
||||
case remoteCredentials = <-conn.remoteOffersCh:
|
||||
// received confirmation from the remote peer -> ready to proceed
|
||||
err = conn.sendAnswer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case remoteCredentials = <-conn.remoteAnswerCh:
|
||||
case <-time.After(conn.config.Timeout):
|
||||
return NewConnectionTimeoutError(conn.config.Key, conn.config.Timeout)
|
||||
case <-conn.closeCh:
|
||||
// closed externally
|
||||
return NewConnectionClosedError(conn.config.Key)
|
||||
}
|
||||
|
||||
log.Debugf("received connection confirmation from peer %s", conn.config.Key)
|
||||
|
||||
// at this point we received offer/answer and we are ready to gather candidates
|
||||
conn.mu.Lock()
|
||||
conn.status = StatusConnecting
|
||||
conn.ctx, conn.notifyDisconnected = context.WithCancel(context.Background())
|
||||
defer conn.notifyDisconnected()
|
||||
conn.mu.Unlock()
|
||||
|
||||
err = conn.agent.GatherCandidates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// will block until connection succeeded
|
||||
// but it won't release if ICE Agent went into Disconnected or Failed state,
|
||||
// so we have to cancel it with the provided context once agent detected a broken connection
|
||||
isControlling := conn.config.LocalKey > conn.config.Key
|
||||
var remoteConn *ice.Conn
|
||||
if isControlling {
|
||||
remoteConn, err = conn.agent.Dial(conn.ctx, remoteCredentials.UFrag, remoteCredentials.Pwd)
|
||||
} else {
|
||||
remoteConn, err = conn.agent.Accept(conn.ctx, remoteCredentials.UFrag, remoteCredentials.Pwd)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// the connection has been established successfully so we are ready to start the proxy
|
||||
err = conn.startProxy(remoteConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("connected to peer %s [laddr <-> raddr] [%s <-> %s]", conn.config.Key, remoteConn.LocalAddr().String(), remoteConn.RemoteAddr().String())
|
||||
|
||||
// wait until connection disconnected or has been closed externally (upper layer, e.g. engine)
|
||||
select {
|
||||
case <-conn.closeCh:
|
||||
// closed externally
|
||||
return NewConnectionClosedError(conn.config.Key)
|
||||
case <-conn.ctx.Done():
|
||||
// disconnected from the remote peer
|
||||
return NewConnectionDisconnectedError(conn.config.Key)
|
||||
}
|
||||
}
|
||||
|
||||
// startProxy starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected
|
||||
func (conn *Conn) startProxy(remoteConn net.Conn) error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
conn.proxy = proxy.NewWireguardProxy(conn.config.ProxyConfig)
|
||||
err := conn.proxy.Start(remoteConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.status = StatusConnected
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanup closes all open resources and sets status to StatusDisconnected
|
||||
func (conn *Conn) cleanup() error {
|
||||
log.Debugf("trying to cleanup %s", conn.config.Key)
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
if conn.agent != nil {
|
||||
err := conn.agent.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.agent = nil
|
||||
}
|
||||
|
||||
if conn.proxy != nil {
|
||||
err := conn.proxy.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.proxy = nil
|
||||
}
|
||||
|
||||
if conn.notifyDisconnected != nil {
|
||||
conn.notifyDisconnected()
|
||||
conn.notifyDisconnected = nil
|
||||
}
|
||||
|
||||
conn.status = StatusDisconnected
|
||||
|
||||
log.Debugf("cleaned up connection to peer %s", conn.config.Key)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSignalOffer sets a handler function to be triggered by Conn when a new connection offer has to be signalled to the remote peer
|
||||
func (conn *Conn) SetSignalOffer(handler func(uFrag string, pwd string) error) {
|
||||
conn.signalOffer = handler
|
||||
}
|
||||
|
||||
// SetSignalAnswer sets a handler function to be triggered by Conn when a new connection answer has to be signalled to the remote peer
|
||||
func (conn *Conn) SetSignalAnswer(handler func(uFrag string, pwd string) error) {
|
||||
conn.signalAnswer = handler
|
||||
}
|
||||
|
||||
// SetSignalCandidate sets a handler function to be triggered by Conn when a new ICE local connection candidate has to be signalled to the remote peer
|
||||
func (conn *Conn) SetSignalCandidate(handler func(candidate ice.Candidate) error) {
|
||||
conn.signalCandidate = handler
|
||||
}
|
||||
|
||||
// onICECandidate is a callback attached to an ICE Agent to receive new local connection candidates
|
||||
// and then signals them to the remote peer
|
||||
func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
||||
if candidate != nil {
|
||||
// log.Debugf("discovered local candidate %s", candidate.String())
|
||||
go func() {
|
||||
err := conn.signalCandidate(candidate)
|
||||
if err != nil {
|
||||
log.Errorf("failed signaling candidate to the remote peer %s %s", conn.config.Key, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *Conn) onICESelectedCandidatePair(c1 ice.Candidate, c2 ice.Candidate) {
|
||||
log.Debugf("selected candidate pair [local <-> remote] -> [%s <-> %s], peer %s", c1.String(), c2.String(),
|
||||
conn.config.Key)
|
||||
}
|
||||
|
||||
// onICEConnectionStateChange registers callback of an ICE Agent to track connection state
|
||||
func (conn *Conn) onICEConnectionStateChange(state ice.ConnectionState) {
|
||||
log.Debugf("peer %s ICE ConnectionState has changed to %s", conn.config.Key, state.String())
|
||||
if state == ice.ConnectionStateFailed || state == ice.ConnectionStateDisconnected {
|
||||
conn.notifyDisconnected()
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *Conn) sendAnswer() error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
localUFrag, localPwd, err := conn.agent.GetLocalUserCredentials()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("sending asnwer to %s", conn.config.Key)
|
||||
err = conn.signalAnswer(localUFrag, localPwd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendOffer prepares local user credentials and signals them to the remote peer
|
||||
func (conn *Conn) sendOffer() error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
localUFrag, localPwd, err := conn.agent.GetLocalUserCredentials()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = conn.signalOffer(localUFrag, localPwd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes this peer Conn issuing a close event to the Conn closeCh
|
||||
func (conn *Conn) Close() error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
select {
|
||||
case conn.closeCh <- struct{}{}:
|
||||
return nil
|
||||
default:
|
||||
// probably could happen when peer has been added and removed right after not even starting to connect
|
||||
// todo further investigate
|
||||
// this really happens due to unordered messages coming from management
|
||||
// more importantly it causes inconsistency -> 2 Conn objects for the same peer
|
||||
// e.g. this flow:
|
||||
// update from management has peers: [1,2,3,4]
|
||||
// engine creates a Conn for peers: [1,2,3,4] and schedules Open in ~1sec
|
||||
// before conn.Open() another update from management arrives with peers: [1,2,3]
|
||||
// engine removes peer 4 and calls conn.Close() which does nothing (this default clause)
|
||||
// before conn.Open() another update from management arrives with peers: [1,2,3,4,5]
|
||||
// engine adds a new Conn for 4 and 5
|
||||
// therefore peer 4 has 2 Conn objects
|
||||
log.Warnf("closing not started coonection %s", conn.config.Key)
|
||||
return NewConnectionAlreadyClosed(conn.config.Key)
|
||||
}
|
||||
}
|
||||
|
||||
// Status returns current status of the Conn
|
||||
func (conn *Conn) Status() ConnStatus {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
return conn.status
|
||||
}
|
||||
|
||||
// OnRemoteOffer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
|
||||
// doesn't block, discards the message if connection wasn't ready
|
||||
func (conn *Conn) OnRemoteOffer(remoteAuth IceCredentials) bool {
|
||||
log.Debugf("OnRemoteOffer from peer %s on status %s", conn.config.Key, conn.status.String())
|
||||
|
||||
select {
|
||||
case conn.remoteOffersCh <- remoteAuth:
|
||||
return true
|
||||
default:
|
||||
log.Debugf("OnRemoteOffer skipping message from peer %s on status %s because is not ready", conn.config.Key, conn.status.String())
|
||||
// connection might not be ready yet to receive so we ignore the message
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// OnRemoteAnswer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
|
||||
// doesn't block, discards the message if connection wasn't ready
|
||||
func (conn *Conn) OnRemoteAnswer(remoteAuth IceCredentials) bool {
|
||||
log.Debugf("OnRemoteAnswer from peer %s on status %s", conn.config.Key, conn.status.String())
|
||||
|
||||
select {
|
||||
case conn.remoteAnswerCh <- remoteAuth:
|
||||
return true
|
||||
default:
|
||||
// connection might not be ready yet to receive so we ignore the message
|
||||
log.Debugf("OnRemoteAnswer skipping message from peer %s on status %s because is not ready", conn.config.Key, conn.status.String())
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// OnRemoteCandidate Handles ICE connection Candidate provided by the remote peer.
|
||||
func (conn *Conn) OnRemoteCandidate(candidate ice.Candidate) {
|
||||
log.Debugf("OnRemoteCandidate from peer %s -> %s", conn.config.Key, candidate.String())
|
||||
go func() {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
if conn.agent == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := conn.agent.AddRemoteCandidate(candidate)
|
||||
if err != nil {
|
||||
log.Errorf("error while handling remote candidate from peer %s", conn.config.Key)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (conn *Conn) GetKey() string {
|
||||
return conn.config.Key
|
||||
}
|
||||
144
client/internal/peer/conn_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"github.com/magiconair/properties/assert"
|
||||
"github.com/pion/ice/v2"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal/proxy"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var connConf = ConnConfig{
|
||||
Key: "LLHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
LocalKey: "RRHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
StunTurn: []*ice.URL{},
|
||||
InterfaceBlackList: nil,
|
||||
Timeout: time.Second,
|
||||
ProxyConfig: proxy.Config{},
|
||||
}
|
||||
|
||||
func TestConn_GetKey(t *testing.T) {
|
||||
conn, err := NewConn(connConf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
got := conn.GetKey()
|
||||
|
||||
assert.Equal(t, got, connConf.Key, "they should be equal")
|
||||
}
|
||||
|
||||
func TestConn_OnRemoteOffer(t *testing.T) {
|
||||
|
||||
conn, err := NewConn(connConf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
<-conn.remoteOffersCh
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
accepted := conn.OnRemoteOffer(IceCredentials{
|
||||
UFrag: "test",
|
||||
Pwd: "test",
|
||||
})
|
||||
if accepted {
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestConn_OnRemoteAnswer(t *testing.T) {
|
||||
|
||||
conn, err := NewConn(connConf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
<-conn.remoteAnswerCh
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
accepted := conn.OnRemoteAnswer(IceCredentials{
|
||||
UFrag: "test",
|
||||
Pwd: "test",
|
||||
})
|
||||
if accepted {
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
func TestConn_Status(t *testing.T) {
|
||||
|
||||
conn, err := NewConn(connConf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tables := []struct {
|
||||
name string
|
||||
status ConnStatus
|
||||
want ConnStatus
|
||||
}{
|
||||
{"StatusConnected", StatusConnected, StatusConnected},
|
||||
{"StatusDisconnected", StatusDisconnected, StatusDisconnected},
|
||||
{"StatusConnecting", StatusConnecting, StatusConnecting},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
t.Run(table.name, func(t *testing.T) {
|
||||
conn.status = table.status
|
||||
|
||||
got := conn.Status()
|
||||
assert.Equal(t, got, table.want, "they should be equal")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_Close(t *testing.T) {
|
||||
|
||||
conn, err := NewConn(connConf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
<-conn.closeCh
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
continue
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
72
client/internal/peer/error.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ConnectionTimeoutError is an error indicating that a peer Conn has been timed out
|
||||
type ConnectionTimeoutError struct {
|
||||
peer string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (e *ConnectionTimeoutError) Error() string {
|
||||
return fmt.Sprintf("connection to peer %s timed out after %s", e.peer, e.timeout.String())
|
||||
}
|
||||
|
||||
// NewConnectionTimeoutError creates a new ConnectionTimeoutError error
|
||||
func NewConnectionTimeoutError(peer string, timeout time.Duration) error {
|
||||
return &ConnectionTimeoutError{
|
||||
peer: peer,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionClosedError is an error indicating that a peer Conn has been forcefully closed
|
||||
type ConnectionClosedError struct {
|
||||
peer string
|
||||
}
|
||||
|
||||
func (e *ConnectionClosedError) Error() string {
|
||||
return fmt.Sprintf("connection to peer %s has been closed", e.peer)
|
||||
}
|
||||
|
||||
// NewConnectionClosedError creates a new ConnectionClosedError error
|
||||
func NewConnectionClosedError(peer string) error {
|
||||
return &ConnectionClosedError{
|
||||
peer: peer,
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionDisconnectedError is an error indicating that a peer Conn has ctx from the remote
|
||||
type ConnectionDisconnectedError struct {
|
||||
peer string
|
||||
}
|
||||
|
||||
func (e *ConnectionDisconnectedError) Error() string {
|
||||
return fmt.Sprintf("disconnected from peer %s", e.peer)
|
||||
}
|
||||
|
||||
// NewConnectionDisconnectedError creates a new ConnectionDisconnectedError error
|
||||
func NewConnectionDisconnectedError(peer string) error {
|
||||
return &ConnectionDisconnectedError{
|
||||
peer: peer,
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionAlreadyClosedError is an error indicating that a peer Conn has been already closed and the invocation of the Close() method has been performed over a closed connection
|
||||
type ConnectionAlreadyClosedError struct {
|
||||
peer string
|
||||
}
|
||||
|
||||
func (e *ConnectionAlreadyClosedError) Error() string {
|
||||
return fmt.Sprintf("connection to peer %s has been already closed", e.peer)
|
||||
}
|
||||
|
||||
// NewConnectionAlreadyClosed creates a new ConnectionAlreadyClosedError error
|
||||
func NewConnectionAlreadyClosed(peer string) error {
|
||||
return &ConnectionAlreadyClosedError{
|
||||
peer: peer,
|
||||
}
|
||||
}
|
||||
27
client/internal/peer/error_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewConnectionClosedError(t *testing.T) {
|
||||
err := NewConnectionClosedError("X")
|
||||
assert.Equal(t, &ConnectionClosedError{peer: "X"}, err)
|
||||
}
|
||||
|
||||
func TestNewConnectionDisconnectedError(t *testing.T) {
|
||||
err := NewConnectionDisconnectedError("X")
|
||||
assert.Equal(t, &ConnectionDisconnectedError{peer: "X"}, err)
|
||||
}
|
||||
|
||||
func TestNewConnectionTimeoutErrorC(t *testing.T) {
|
||||
err := NewConnectionTimeoutError("X", time.Second)
|
||||
assert.Equal(t, &ConnectionTimeoutError{peer: "X", timeout: time.Second}, err)
|
||||
}
|
||||
|
||||
func TestNewConnectionAlreadyClosed(t *testing.T) {
|
||||
err := NewConnectionAlreadyClosed("X")
|
||||
assert.Equal(t, &ConnectionAlreadyClosedError{peer: "X"}, err)
|
||||
}
|
||||
25
client/internal/peer/status.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package peer
|
||||
|
||||
import log "github.com/sirupsen/logrus"
|
||||
|
||||
type ConnStatus int
|
||||
|
||||
func (s ConnStatus) String() string {
|
||||
switch s {
|
||||
case StatusConnecting:
|
||||
return "StatusConnecting"
|
||||
case StatusConnected:
|
||||
return "StatusConnected"
|
||||
case StatusDisconnected:
|
||||
return "StatusDisconnected"
|
||||
default:
|
||||
log.Errorf("unknown status: %d", s)
|
||||
return "INVALID_PEER_CONNECTION_STATUS"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
StatusConnected = iota
|
||||
StatusConnecting
|
||||
StatusDisconnected
|
||||
)
|
||||
27
client/internal/peer/status_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"github.com/magiconair/properties/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConnStatus_String(t *testing.T) {
|
||||
|
||||
tables := []struct {
|
||||
name string
|
||||
status ConnStatus
|
||||
want string
|
||||
}{
|
||||
{"StatusConnected", StatusConnected, "StatusConnected"},
|
||||
{"StatusDisconnected", StatusDisconnected, "StatusDisconnected"},
|
||||
{"StatusConnecting", StatusConnecting, "StatusConnecting"},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
t.Run(table.name, func(t *testing.T) {
|
||||
got := table.status.String()
|
||||
assert.Equal(t, got, table.want, "they should be equal")
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
68
client/internal/proxy/dummy.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DummyProxy just sends pings to the RemoteKey peer and reads responses
|
||||
type DummyProxy struct {
|
||||
conn net.Conn
|
||||
remote string
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewDummyProxy(remote string) *DummyProxy {
|
||||
p := &DummyProxy{remote: remote}
|
||||
p.ctx, p.cancel = context.WithCancel(context.Background())
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *DummyProxy) Close() error {
|
||||
p.cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DummyProxy) Start(remoteConn net.Conn) error {
|
||||
p.conn = remoteConn
|
||||
go func() {
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return
|
||||
default:
|
||||
_, err := p.conn.Read(buf)
|
||||
if err != nil {
|
||||
log.Errorf("error while reading RemoteKey %s proxy %v", p.remote, err)
|
||||
return
|
||||
}
|
||||
//log.Debugf("received %s from %s", string(buf[:n]), p.remote)
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return
|
||||
default:
|
||||
_, err := p.conn.Write([]byte("hello"))
|
||||
//log.Debugf("sent ping to %s", p.remote)
|
||||
if err != nil {
|
||||
log.Errorf("error while writing to RemoteKey %s proxy %v", p.remote, err)
|
||||
return
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
25
client/internal/proxy/proxy.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DefaultWgKeepAlive = 25 * time.Second
|
||||
|
||||
type Config struct {
|
||||
WgListenAddr string
|
||||
RemoteKey string
|
||||
WgInterface iface.WGIface
|
||||
AllowedIps string
|
||||
PreSharedKey *wgtypes.Key
|
||||
}
|
||||
|
||||
type Proxy interface {
|
||||
io.Closer
|
||||
// Start creates a local remoteConn and starts proxying data from/to remoteConn
|
||||
Start(remoteConn net.Conn) error
|
||||
}
|
||||
124
client/internal/proxy/wireguard.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net"
|
||||
)
|
||||
|
||||
// WireguardProxy proxies
|
||||
type WireguardProxy struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
config Config
|
||||
|
||||
remoteConn net.Conn
|
||||
localConn net.Conn
|
||||
}
|
||||
|
||||
func NewWireguardProxy(config Config) *WireguardProxy {
|
||||
p := &WireguardProxy{config: config}
|
||||
p.ctx, p.cancel = context.WithCancel(context.Background())
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *WireguardProxy) updateEndpoint() error {
|
||||
udpAddr, err := net.ResolveUDPAddr(p.localConn.LocalAddr().Network(), p.localConn.LocalAddr().String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// add local proxy connection as a Wireguard peer
|
||||
err = p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
|
||||
udpAddr, p.config.PreSharedKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *WireguardProxy) Start(remoteConn net.Conn) error {
|
||||
p.remoteConn = remoteConn
|
||||
|
||||
var err error
|
||||
p.localConn, err = net.Dial("udp", p.config.WgListenAddr)
|
||||
if err != nil {
|
||||
log.Errorf("failed dialing to local Wireguard port %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.updateEndpoint()
|
||||
if err != nil {
|
||||
log.Errorf("error while updating Wireguard peer endpoint [%s] %v", p.config.RemoteKey, err)
|
||||
return err
|
||||
}
|
||||
|
||||
go p.proxyToRemote()
|
||||
go p.proxyToLocal()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *WireguardProxy) Close() error {
|
||||
p.cancel()
|
||||
if c := p.localConn; c != nil {
|
||||
err := p.localConn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := p.config.WgInterface.RemovePeer(p.config.RemoteKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// proxyToRemote proxies everything from Wireguard to the RemoteKey peer
|
||||
// blocks
|
||||
func (p *WireguardProxy) proxyToRemote() {
|
||||
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
log.Debugf("stopped proxying to remote peer %s due to closed connection", p.config.RemoteKey)
|
||||
return
|
||||
default:
|
||||
n, err := p.localConn.Read(buf)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = p.remoteConn.Write(buf[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// proxyToLocal proxies everything from the RemoteKey peer to local Wireguard
|
||||
// blocks
|
||||
func (p *WireguardProxy) proxyToLocal() {
|
||||
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
log.Debugf("stopped proxying from remote peer %s due to closed connection", p.config.RemoteKey)
|
||||
return
|
||||
default:
|
||||
n, err := p.remoteConn.Read(buf)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = p.localConn.Write(buf[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
ice "github.com/pion/ice/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
"net"
|
||||
)
|
||||
|
||||
// WgProxy an instance of an instance of the Connection Wireguard Proxy
|
||||
type WgProxy struct {
|
||||
iface string
|
||||
remoteKey string
|
||||
allowedIps string
|
||||
wgAddr string
|
||||
close chan struct{}
|
||||
wgConn net.Conn
|
||||
}
|
||||
|
||||
// NewWgProxy creates a new Connection Wireguard Proxy
|
||||
func NewWgProxy(iface string, remoteKey string, allowedIps string, wgAddr string) *WgProxy {
|
||||
return &WgProxy{
|
||||
iface: iface,
|
||||
remoteKey: remoteKey,
|
||||
allowedIps: allowedIps,
|
||||
wgAddr: wgAddr,
|
||||
close: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the proxy
|
||||
func (p *WgProxy) Close() error {
|
||||
|
||||
close(p.close)
|
||||
if c := p.wgConn; c != nil {
|
||||
err := p.wgConn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := iface.RemovePeer(p.iface, p.remoteKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartLocal configure the interface with a peer using a direct IP:Port endpoint to the remote host
|
||||
func (p *WgProxy) StartLocal(host string) error {
|
||||
err := iface.UpdatePeer(p.iface, p.remoteKey, p.allowedIps, DefaultWgKeepAlive, host)
|
||||
if err != nil {
|
||||
log.Errorf("error while configuring Wireguard peer [%s] %s", p.remoteKey, err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts a new proxy using the ICE connection
|
||||
func (p *WgProxy) Start(remoteConn *ice.Conn) error {
|
||||
|
||||
wgConn, err := net.Dial("udp", p.wgAddr)
|
||||
if err != nil {
|
||||
log.Fatalf("failed dialing to local Wireguard port %s", err)
|
||||
return err
|
||||
}
|
||||
p.wgConn = wgConn
|
||||
// add local proxy connection as a Wireguard peer
|
||||
err = iface.UpdatePeer(p.iface, p.remoteKey, p.allowedIps, DefaultWgKeepAlive,
|
||||
wgConn.LocalAddr().String())
|
||||
if err != nil {
|
||||
log.Errorf("error while configuring Wireguard peer [%s] %s", p.remoteKey, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
go func() { p.proxyToRemotePeer(remoteConn) }()
|
||||
go func() { p.proxyToLocalWireguard(remoteConn) }()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// proxyToRemotePeer proxies everything from Wireguard to the remote peer
|
||||
// blocks
|
||||
func (p *WgProxy) proxyToRemotePeer(remoteConn *ice.Conn) {
|
||||
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
select {
|
||||
case <-p.close:
|
||||
log.Infof("stopped proxying from remote peer %s due to closed connection", p.remoteKey)
|
||||
return
|
||||
default:
|
||||
n, err := p.wgConn.Read(buf)
|
||||
if err != nil {
|
||||
//log.Warnln("failed reading from peer: ", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = remoteConn.Write(buf[:n])
|
||||
if err != nil {
|
||||
//log.Warnln("failed writing to remote peer: ", err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// proxyToLocalWireguard proxies everything from the remote peer to local Wireguard
|
||||
// blocks
|
||||
func (p *WgProxy) proxyToLocalWireguard(remoteConn *ice.Conn) {
|
||||
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
select {
|
||||
case <-p.close:
|
||||
log.Infof("stopped proxying from remote peer %s due to closed connection", p.remoteKey)
|
||||
return
|
||||
default:
|
||||
n, err := remoteConn.Read(buf)
|
||||
if err != nil {
|
||||
//log.Errorf("failed reading from remote connection %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = p.wgConn.Write(buf[:n])
|
||||
if err != nil {
|
||||
//log.Errorf("failed writing to local Wireguard instance %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wiretrustee/wiretrustee/client/cmd"
|
||||
"os"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/client/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
#define STRINGIZE(x) #x
|
||||
#define EXPAND(x) STRINGIZE(x)
|
||||
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
|
||||
wintun.dll RCDATA wintun.dll
|
||||
|
||||
7 ICON ui/wiretrustee.ico
|
||||
wireguard.dll RCDATA wireguard.dll
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
package system
|
||||
|
||||
// this is the wiretrustee version
|
||||
// will be replaced with the release version when using goreleaser
|
||||
var version = "development"
|
||||
|
||||
//Info is an object that contains machine information
|
||||
// Most of the code is taken from https://github.com/matishsiao/goInfo
|
||||
type Info struct {
|
||||
GoOS string
|
||||
Kernel string
|
||||
Core string
|
||||
Platform string
|
||||
OS string
|
||||
OSVersion string
|
||||
Hostname string
|
||||
CPUs int
|
||||
GoOS string
|
||||
Kernel string
|
||||
Core string
|
||||
Platform string
|
||||
OS string
|
||||
OSVersion string
|
||||
Hostname string
|
||||
CPUs int
|
||||
WiretrusteeVersion string
|
||||
}
|
||||
|
||||
func WiretrusteeVersion() string {
|
||||
return version
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ func GetInfo() *Info {
|
||||
osInfo := strings.Split(osStr, " ")
|
||||
gio := &Info{Kernel: osInfo[0], OSVersion: osInfo[1], Core: osInfo[1], Platform: osInfo[2], OS: osInfo[0], GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
||||
gio.Hostname, _ = os.Hostname()
|
||||
gio.WiretrusteeVersion = WiretrusteeVersion()
|
||||
return gio
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ func GetInfo() *Info {
|
||||
osInfo := strings.Split(osStr, " ")
|
||||
gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: runtime.GOARCH, OS: osInfo[2], GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
||||
gio.Hostname, _ = os.Hostname()
|
||||
gio.WiretrusteeVersion = WiretrusteeVersion()
|
||||
return gio
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@ func GetInfo() *Info {
|
||||
}
|
||||
gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: osInfo[2], OS: osName, OSVersion: osVer, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
||||
gio.Hostname, _ = os.Hostname()
|
||||
gio.WiretrusteeVersion = WiretrusteeVersion()
|
||||
|
||||
return gio
|
||||
}
|
||||
|
||||
|
||||
13
client/system/info_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_LocalVersion(t *testing.T) {
|
||||
got := GetInfo()
|
||||
want := "development"
|
||||
assert.Equal(t, want, got.WiretrusteeVersion)
|
||||
}
|
||||
@@ -31,5 +31,7 @@ func GetInfo() *Info {
|
||||
}
|
||||
gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
||||
gio.Hostname, _ = os.Hostname()
|
||||
gio.WiretrusteeVersion = WiretrusteeVersion()
|
||||
|
||||
return gio
|
||||
}
|
||||
|
||||
68
client/testdata/management.json
vendored
@@ -1,37 +1,37 @@
|
||||
{
|
||||
"Stuns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "stun:stun.wiretrustee.com:3468",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
}
|
||||
],
|
||||
"TURNConfig": {
|
||||
"Turns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "turn:stun.wiretrustee.com:3468",
|
||||
"Username": "some_user",
|
||||
"Password": "c29tZV9wYXNzd29yZA=="
|
||||
}
|
||||
],
|
||||
"CredentialsTTL": "1h",
|
||||
"Secret": "c29tZV9wYXNzd29yZA==",
|
||||
"TimeBasedCredentials": true
|
||||
},
|
||||
"Signal": {
|
||||
"Proto": "http",
|
||||
"URI": "signal.wiretrustee.com:10000",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
},
|
||||
"DataDir": "",
|
||||
"HttpConfig": {
|
||||
"LetsEncryptDomain": "<PASTE YOUR LET'S ENCRYPT DOMAIN HERE>",
|
||||
"Address": "0.0.0.0:33071",
|
||||
"AuthIssuer": "<PASTE YOUR AUTH0 ISSUER HERE>,",
|
||||
"AuthAudience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
|
||||
"AuthKeysLocation": "<PASTE YOUR AUTH0 PUBLIC JWT KEYS LOCATION HERE>"
|
||||
"Stuns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "stun:stun.wiretrustee.com:3468",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
}
|
||||
],
|
||||
"TURNConfig": {
|
||||
"Turns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "turn:stun.wiretrustee.com:3468",
|
||||
"Username": "some_user",
|
||||
"Password": "c29tZV9wYXNzd29yZA=="
|
||||
}
|
||||
],
|
||||
"CredentialsTTL": "1h",
|
||||
"Secret": "c29tZV9wYXNzd29yZA==",
|
||||
"TimeBasedCredentials": true
|
||||
},
|
||||
"Signal": {
|
||||
"Proto": "http",
|
||||
"URI": "signal.wiretrustee.com:10000",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
},
|
||||
"DataDir": "",
|
||||
"HttpConfig": {
|
||||
"LetsEncryptDomain": "<PASTE YOUR LET'S ENCRYPT DOMAIN HERE>",
|
||||
"Address": "0.0.0.0:33071",
|
||||
"AuthIssuer": "<PASTE YOUR AUTH0 ISSUER HERE>,",
|
||||
"AuthAudience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
|
||||
"AuthKeysLocation": "<PASTE YOUR AUTH0 PUBLIC JWT KEYS LOCATION HERE>"
|
||||
}
|
||||
}
|
||||
13
client/testdata/store.json
vendored
@@ -11,6 +11,7 @@
|
||||
"ExpiresAt": "2321-09-18T20:46:20.005936822+02:00",
|
||||
"Revoked": false,
|
||||
"UsedTimes": 0
|
||||
|
||||
}
|
||||
},
|
||||
"Network": {
|
||||
@@ -21,7 +22,17 @@
|
||||
},
|
||||
"Dns": null
|
||||
},
|
||||
"Peers": {}
|
||||
"Peers": {},
|
||||
"Users": {
|
||||
"edafee4e-63fb-11ec-90d6-0242ac120003": {
|
||||
"Id": "edafee4e-63fb-11ec-90d6-0242ac120003",
|
||||
"Role": "admin"
|
||||
},
|
||||
"f4f6d672-63fb-11ec-90d6-0242ac120003": {
|
||||
"Id": "f4f6d672-63fb-11ec-90d6-0242ac120003",
|
||||
"Role": "user"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
client/wireguard_nt.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
ldir=$PWD
|
||||
tmp_dir_path=$ldir/.distfiles
|
||||
winnt=wireguard-nt.zip
|
||||
download_file_path=$tmp_dir_path/$winnt
|
||||
download_url=https://download.wireguard.com/wireguard-nt/wireguard-nt-0.10.1.zip
|
||||
download_sha=772c0b1463d8d2212716f43f06f4594d880dea4f735165bd68e388fc41b81605
|
||||
|
||||
function resources_windows(){
|
||||
cmd=$1
|
||||
arch=$2
|
||||
out=$3
|
||||
docker run -i --rm -v $PWD:$PWD -w $PWD mstorsjo/llvm-mingw:latest $cmd -O coff -c 65001 -I $tmp_dir_path/wireguard-nt/bin/$arch -i resources.rc -o $out
|
||||
}
|
||||
|
||||
mkdir -p $tmp_dir_path
|
||||
curl -L#o $download_file_path.unverified $download_url
|
||||
echo "$download_sha $download_file_path.unverified" | sha256sum -c
|
||||
mv $download_file_path.unverified $download_file_path
|
||||
|
||||
mkdir -p .deps
|
||||
unzip $download_file_path -d $tmp_dir_path
|
||||
|
||||
resources_windows i686-w64-mingw32-windres x86 resources_windows_386.syso
|
||||
resources_windows aarch64-w64-mingw32-windres arm64 resources_windows_arm64.syso
|
||||
resources_windows x86_64-w64-mingw32-windres amd64 resources_windows_amd64.syso
|
||||
104
docs/README.md
Normal file
@@ -0,0 +1,104 @@
|
||||
### Table of contents
|
||||
|
||||
* [About Wiretrustee](#about-wiretrustee)
|
||||
* [Why Wireguard with Wiretrustee?](#why-wireguard-with-wiretrustee)
|
||||
* [Wiretrustee vs. Traditional VPN](#wiretrustee-vs-traditional-vpn)
|
||||
* [High-level technology overview](#high-level-technology-overview)
|
||||
* [Getting started](#getting-started)
|
||||
|
||||
### About Wiretrustee
|
||||
|
||||
Wiretrustee 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 Wiretrustee - 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 Wiretrustee. Check our [Quickstart Guide Video](https://www.youtube.com/watch?v=cWTsGUJAUaU) to see the setup in action.
|
||||
|
||||
### Why Wireguard with Wiretrustee?
|
||||
|
||||
WireGuard is a modern and extremely fast VPN tunnel utilizing state-of-the-art [cryptography](https://www.wireguard.com/protocol/)
|
||||
and Wiretrustee 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.
|
||||
|
||||
Wiretrustee 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, Wiretrustee 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.
|
||||
Wiretrustee 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 Wiretrustee.
|
||||
|
||||
### Wiretrustee 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.
|
||||
|
||||
Wiretrustee 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, Wiretrustee 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-wiretrustee) section above,
|
||||
Wiretrustee installation doesn't require complex network and firewall configuration.
|
||||
It just works, minimising the maintenance effort.
|
||||
|
||||
Finally, each machine or device in the Wiretrustee 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, Wiretrustee 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>
|
||||
|
||||
Wiretrustee 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/wiretrustee/wiretrustee) developed by Wiretrustee authors to make it all work together.
|
||||
|
||||
To learn more about Wiretrustee architecture, please refer to the [architecture section](../docs/architecture.md).
|
||||
|
||||
### Getting Started
|
||||
|
||||
There are 2 ways of getting started with Wiretrustee:
|
||||
- use Cloud Managed version
|
||||
- self-hosting
|
||||
|
||||
We recommend starting with the cloud managed version hosted at [app.wiretrustee.com](https://app.wiretrustee.com) - 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).
|
||||
2
docs/architecture.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Architecture
|
||||
TODO
|
||||
BIN
docs/media/add-peer.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
docs/media/auth.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/media/empty-peers.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/media/high-level-dia.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
docs/media/logo-full.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/media/logo.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/media/peerA.gif
Normal file
|
After Width: | Height: | Size: 409 KiB |
BIN
docs/media/peerB.gif
Normal file
|
After Width: | Height: | Size: 526 KiB |
|
Before Width: | Height: | Size: 5.9 MiB After Width: | Height: | Size: 5.9 MiB |
BIN
docs/media/peers.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
41
docs/quickstart.md
Normal file
@@ -0,0 +1,41 @@
|
||||
## Quickstart guide (Cloud Managed version)
|
||||
Step-by-step video guide on YouTube:
|
||||
|
||||
[](https://youtu.be/cWTsGUJAUaU "Wiretrustee - 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.wiretrustee.com/](https://app.wiretrustee.com/peers)
|
||||
|
||||
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.
|
||||
96
docs/self-hosting.md
Normal file
@@ -0,0 +1,96 @@
|
||||
### Self-hosting
|
||||
Wiretrustee is an open-source platform that can be self-hosted on your servers.
|
||||
|
||||
It relies on components developed by Wiretrustee Authors [Management Service](https://github.com/wiretrustee/wiretrustee/tree/main/management), [Management UI Dashboard](https://github.com/wiretrustee/wiretrustee-dashboard), [Signal Service](https://github.com/wiretrustee/wiretrustee/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.
|
||||
|
||||
If you would like to learn more about the architecture please refer to the [Wiretrustee Architecture section](architecture.md).
|
||||
|
||||
### Step-by-step video guide on YouTube:
|
||||
|
||||
[](https://youtu.be/Ofpgx5WhT0k "Wiretrustee Self-Hosting Guide")
|
||||
|
||||
### Requirements
|
||||
|
||||
- Virtual machine offered by any cloud provider (e.g., AWS, DigitalOcean, Hetzner, Google Cloud, Azure ...).
|
||||
- Any Linux 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.
|
||||
- Open ports ```443, 33071, 33073, 10000, 3478``` (Dashboard, Management HTTP API, Management gRpc API, Signal gRpc, Coturn STUN/TURN respectively) on your server.
|
||||
- Maybe a cup of coffee or tea :)
|
||||
|
||||
### Step-by-step guide
|
||||
|
||||
For this tutorial we will be using domain ```test.wiretrustee.com``` 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 Wiretrustee repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/wiretrustee/wiretrustee.git wiretrustee/
|
||||
```
|
||||
|
||||
and switch to the ```wiretrustee/infrastructure_files/``` folder that contains docker compose file:
|
||||
|
||||
```bash
|
||||
cd wiretrustee/infrastructure_files/
|
||||
```
|
||||
3. Prepare configuration files.
|
||||
|
||||
To simplify the setup we have prepared a script to substitute required properties in the [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
|
||||
# e.g. app.mydomain.com
|
||||
WIRETRUSTEE_DOMAIN=""
|
||||
# e.g. dev-24vkclam.us.auth0.com
|
||||
WIRETRUSTEE_AUTH0_DOMAIN=""
|
||||
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
|
||||
WIRETRUSTEE_AUTH0_CLIENT_ID=""
|
||||
# e.g. https://app.mydomain.com/
|
||||
WIRETRUSTEE_AUTH0_AUDIENCE=""
|
||||
# e.g. hello@mydomain.com
|
||||
WIRETRUSTEE_LETSENCRYPT_EMAIL=""
|
||||
```
|
||||
|
||||
Please follow the steps to get the values.
|
||||
|
||||
4. Configure ```WIRETRUSTEE_AUTH0_DOMAIN``` ```WIRETRUSTEE_AUTH0_CLIENT_ID``` ```WIRETRUSTEE_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 ```WIRETRUSTEE_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 ```WIRETRUSTEE_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
|
||||
76
go.mod
@@ -1,27 +1,71 @@
|
||||
module github.com/wiretrustee/wiretrustee
|
||||
|
||||
go 1.16
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v4 v4.1.0
|
||||
github.com/cenkalti/backoff/v4 v4.1.2
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/kardianos/service v1.2.0
|
||||
github.com/onsi/ginkgo v1.16.4
|
||||
github.com/onsi/gomega v1.13.0
|
||||
github.com/pion/ice/v2 v2.1.7
|
||||
github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7 //keep this version otherwise wiretrustee up command breaks
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.17.0
|
||||
github.com/pion/ice/v2 v2.1.17
|
||||
github.com/rs/cors v1.8.0
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.3.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
|
||||
golang.zx2c4.com/wireguard/windows v0.4.5
|
||||
google.golang.org/grpc v1.32.0
|
||||
google.golang.org/protobuf v1.26.0
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
||||
golang.zx2c4.com/wireguard/windows v0.5.1
|
||||
google.golang.org/grpc v1.43.0
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/magiconair/properties v1.8.5
|
||||
github.com/rs/xid v1.3.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||
github.com/mdlayher/genetlink v1.1.0 // indirect
|
||||
github.com/mdlayher/netlink v1.4.2 // indirect
|
||||
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/pion/dtls/v2 v2.1.2 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/mdns v0.0.5 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/stun v0.3.5 // indirect
|
||||
github.com/pion/transport v0.13.0 // indirect
|
||||
github.com/pion/turn/v2 v2.0.7 // indirect
|
||||
github.com/pion/udp v0.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
|
||||
golang.org/x/tools v0.1.8 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
honnef.co/go/tools v0.2.2 // indirect
|
||||
)
|
||||
|
||||
replace github.com/pion/ice/v2 => github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb
|
||||
|
||||
133
iface/configuration.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// configureDevice configures the wireguard device
|
||||
func (w *WGIface) configureDevice(config wgtypes.Config) error {
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wg.Close()
|
||||
|
||||
// validate if device with name exists
|
||||
_, err = wg.Device(w.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("got Wireguard device %s", w.Name)
|
||||
|
||||
return wg.ConfigureDevice(w.Name, config)
|
||||
}
|
||||
|
||||
// Configure configures a Wireguard interface
|
||||
// The interface must exist before calling this method (e.g. call interface.Create() before)
|
||||
func (w *WGIface) Configure(privateKey string, port int) error {
|
||||
|
||||
log.Debugf("configuring Wireguard interface %s", w.Name)
|
||||
|
||||
log.Debugf("adding Wireguard private key")
|
||||
key, err := wgtypes.ParseKey(privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fwmark := 0
|
||||
config := wgtypes.Config{
|
||||
PrivateKey: &key,
|
||||
ReplacePeers: true,
|
||||
FirewallMark: &fwmark,
|
||||
ListenPort: &port,
|
||||
}
|
||||
|
||||
err = w.configureDevice(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("received error \"%v\" while configuring interface %s with port %d", err, w.Name, port)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetListenPort returns the listening port of the Wireguard endpoint
|
||||
func (w *WGIface) GetListenPort() (*int, error) {
|
||||
log.Debugf("getting Wireguard listen port of interface %s", w.Name)
|
||||
|
||||
//discover Wireguard current configuration
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer wg.Close()
|
||||
|
||||
d, err := wg.Device(w.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("got Wireguard device listen port %s, %d", w.Name, d.ListenPort)
|
||||
|
||||
return &d.ListenPort, nil
|
||||
}
|
||||
|
||||
// UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist
|
||||
// Endpoint is optional
|
||||
func (w *WGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
||||
|
||||
log.Debugf("updating interface %s peer %s: endpoint %s ", w.Name, peerKey, endpoint)
|
||||
|
||||
//parse allowed ips
|
||||
_, ipNet, err := net.ParseCIDR(allowedIps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
peer := wgtypes.PeerConfig{
|
||||
PublicKey: peerKeyParsed,
|
||||
ReplaceAllowedIPs: true,
|
||||
AllowedIPs: []net.IPNet{*ipNet},
|
||||
PersistentKeepaliveInterval: &keepAlive,
|
||||
PresharedKey: preSharedKey,
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
|
||||
config := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{peer},
|
||||
}
|
||||
err = w.configureDevice(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("received error \"%v\" while updating peer on interface %s with settings: allowed ips %s, endpoint %s", err, w.Name, allowedIps, endpoint.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePeer removes a Wireguard Peer from the interface iface
|
||||
func (w *WGIface) RemovePeer(peerKey string) error {
|
||||
log.Debugf("Removing peer %s from interface %s ", peerKey, w.Name)
|
||||
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peer := wgtypes.PeerConfig{
|
||||
PublicKey: peerKeyParsed,
|
||||
Remove: true,
|
||||
}
|
||||
|
||||
config := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{peer},
|
||||
}
|
||||
err = w.configureDevice(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("received error \"%v\" while removing peer %s from interface %s", err, peerKey, w.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
240
iface/iface.go
@@ -1,80 +1,51 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"net"
|
||||
"time"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMTU = 1280
|
||||
DefaultMTU = 1280
|
||||
)
|
||||
|
||||
var (
|
||||
tunIface tun.Device
|
||||
// todo check after move the WgPort constant to the client
|
||||
WgPort = 51820
|
||||
)
|
||||
|
||||
// CreateWithUserspace Creates a new Wireguard interface, using wireguard-go userspace implementation
|
||||
func CreateWithUserspace(iface string, address string) error {
|
||||
var err error
|
||||
tunIface, err = tun.CreateTUN(iface, defaultMTU)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We need to create a wireguard-go device and listen to configuration requests
|
||||
tunDevice := device.NewDevice(tunIface, conn.NewDefaultBind(), device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
|
||||
err = tunDevice.Up()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uapi, err := getUAPI(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
uapiConn, err := uapi.Accept()
|
||||
if err != nil {
|
||||
log.Debugln("uapi Accept failed with error: ", err)
|
||||
continue
|
||||
}
|
||||
go tunDevice.IpcHandle(uapiConn)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debugln("UAPI listener started")
|
||||
|
||||
err = assignAddr(address, iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
// WGIface represents a interface instance
|
||||
type WGIface struct {
|
||||
Name string
|
||||
Port int
|
||||
MTU int
|
||||
Address WGAddress
|
||||
Interface NetInterface
|
||||
}
|
||||
|
||||
// configure peer for the wireguard device
|
||||
func configureDevice(iface string, config wgtypes.Config) error {
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wg.Close()
|
||||
// WGAddress Wireguard parsed address
|
||||
type WGAddress struct {
|
||||
IP net.IP
|
||||
Network *net.IPNet
|
||||
}
|
||||
|
||||
_, err = wg.Device(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("got Wireguard device %s", iface)
|
||||
// NetInterface represents a generic network tunnel interface
|
||||
type NetInterface interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
return wg.ConfigureDevice(iface, config)
|
||||
// NewWGIface Creates a new Wireguard interface instance
|
||||
func NewWGIface(iface string, address string, mtu int) (WGIface, error) {
|
||||
wgIface := WGIface{
|
||||
Name: iface,
|
||||
MTU: mtu,
|
||||
}
|
||||
|
||||
wgAddress, err := parseAddress(address)
|
||||
if err != nil {
|
||||
return wgIface, err
|
||||
}
|
||||
|
||||
wgIface.Address = wgAddress
|
||||
|
||||
return wgIface, nil
|
||||
}
|
||||
|
||||
// Exists checks whether specified Wireguard device exists or not
|
||||
@@ -101,140 +72,35 @@ func Exists(iface string) (*bool, error) {
|
||||
return &exists, nil
|
||||
}
|
||||
|
||||
// Configure configures a Wireguard interface
|
||||
// The interface must exist before calling this method (e.g. call interface.Create() before)
|
||||
func Configure(iface string, privateKey string) error {
|
||||
|
||||
log.Debugf("configuring Wireguard interface %s", iface)
|
||||
|
||||
log.Debugf("adding Wireguard private key")
|
||||
key, err := wgtypes.ParseKey(privateKey)
|
||||
// parseAddress parse a string ("1.2.3.4/24") address to WG Address
|
||||
func parseAddress(address string) (WGAddress, error) {
|
||||
ip, network, err := net.ParseCIDR(address)
|
||||
if err != nil {
|
||||
return err
|
||||
return WGAddress{}, err
|
||||
}
|
||||
fwmark := 0
|
||||
p := WgPort
|
||||
config := wgtypes.Config{
|
||||
PrivateKey: &key,
|
||||
ReplacePeers: false,
|
||||
FirewallMark: &fwmark,
|
||||
ListenPort: &p,
|
||||
}
|
||||
|
||||
return configureDevice(iface, config)
|
||||
return WGAddress{
|
||||
IP: ip,
|
||||
Network: network,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetListenPort returns the listening port of the Wireguard endpoint
|
||||
func GetListenPort(iface string) (*int, error) {
|
||||
log.Debugf("getting Wireguard listen port of interface %s", iface)
|
||||
// Closes the tunnel interface
|
||||
func (w *WGIface) Close() error {
|
||||
|
||||
//discover Wireguard current configuration
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer wg.Close()
|
||||
|
||||
d, err := wg.Device(iface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("got Wireguard device listen port %s, %d", iface, d.ListenPort)
|
||||
|
||||
return &d.ListenPort, nil
|
||||
}
|
||||
|
||||
// UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist
|
||||
// Endpoint is optional
|
||||
func UpdatePeer(iface string, peerKey string, allowedIps string, keepAlive time.Duration, endpoint string) error {
|
||||
|
||||
log.Debugf("updating interface %s peer %s: endpoint %s ", iface, peerKey, endpoint)
|
||||
|
||||
//parse allowed ips
|
||||
_, ipNet, err := net.ParseCIDR(allowedIps)
|
||||
err := w.Interface.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
peer := wgtypes.PeerConfig{
|
||||
PublicKey: peerKeyParsed,
|
||||
ReplaceAllowedIPs: true,
|
||||
AllowedIPs: []net.IPNet{*ipNet},
|
||||
PersistentKeepaliveInterval: &keepAlive,
|
||||
}
|
||||
|
||||
config := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{peer},
|
||||
}
|
||||
|
||||
err = configureDevice(iface, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if endpoint != "" {
|
||||
return UpdatePeerEndpoint(iface, peerKey, endpoint)
|
||||
if runtime.GOOS == "darwin" {
|
||||
sockPath := "/var/run/wireguard/" + w.Name + ".sock"
|
||||
if _, statErr := os.Stat(sockPath); statErr == nil {
|
||||
statErr = os.Remove(sockPath)
|
||||
if statErr != nil {
|
||||
return statErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePeerEndpoint updates a Wireguard interface Peer with the new endpoint
|
||||
// Used when NAT hole punching was successful and an update of the remote peer endpoint is required
|
||||
func UpdatePeerEndpoint(iface string, peerKey string, newEndpoint string) error {
|
||||
|
||||
log.Debugf("updating peer %s endpoint %s ", peerKey, newEndpoint)
|
||||
|
||||
peerAddr, err := net.ResolveUDPAddr("udp4", newEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("parsed peer endpoint [%s]", peerAddr.String())
|
||||
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peer := wgtypes.PeerConfig{
|
||||
PublicKey: peerKeyParsed,
|
||||
ReplaceAllowedIPs: false,
|
||||
UpdateOnly: true,
|
||||
Endpoint: peerAddr,
|
||||
}
|
||||
config := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{peer},
|
||||
}
|
||||
return configureDevice(iface, config)
|
||||
}
|
||||
|
||||
// RemovePeer removes a Wireguard Peer from the interface iface
|
||||
func RemovePeer(iface string, peerKey string) error {
|
||||
log.Debugf("Removing peer %s from interface %s ", peerKey, iface)
|
||||
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peer := wgtypes.PeerConfig{
|
||||
PublicKey: peerKeyParsed,
|
||||
Remove: true,
|
||||
}
|
||||
|
||||
config := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{peer},
|
||||
}
|
||||
|
||||
return configureDevice(iface, config)
|
||||
}
|
||||
|
||||
// Closes the User Space tunnel interface
|
||||
func CloseWithUserspace() error {
|
||||
return tunIface.Close()
|
||||
}
|
||||
|
||||
@@ -2,62 +2,31 @@ package iface
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Create Creates a new Wireguard interface, sets a given IP and brings it up.
|
||||
func Create(iface string, address string) error {
|
||||
return CreateWithUserspace(iface, address)
|
||||
func (w *WGIface) Create() error {
|
||||
return w.CreateWithUserspace()
|
||||
}
|
||||
|
||||
// assignAddr Adds IP address to the tunnel interface and network route based on the range provided
|
||||
func assignAddr(address string, ifaceName string) error {
|
||||
ip := strings.Split(address, "/")
|
||||
cmd := exec.Command("ifconfig", ifaceName, "inet", address, ip[0])
|
||||
func (w *WGIface) assignAddr() error {
|
||||
//mask,_ := w.Address.Network.Mask.Size()
|
||||
//
|
||||
//address := fmt.Sprintf("%s/%d",w.Address.IP.String() , mask)
|
||||
|
||||
cmd := exec.Command("ifconfig", w.Name, "inet", w.Address.IP.String(), w.Address.IP.String())
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
log.Infof("Command: %v failed with output %s and error: ", cmd.String(), out)
|
||||
log.Infof("adding addreess command \"%v\" failed with output %s and error: ", cmd.String(), out)
|
||||
return err
|
||||
}
|
||||
_, resolvedNet, err := net.ParseCIDR(address)
|
||||
err = addRoute(ifaceName, resolvedNet)
|
||||
if err != nil {
|
||||
log.Infoln("Adding route failed with error:", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// addRoute Adds network route based on the range provided
|
||||
func addRoute(iface string, ipNet *net.IPNet) error {
|
||||
cmd := exec.Command("route", "add", "-net", ipNet.String(), "-interface", iface)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
log.Printf("Command: %v failed with output %s and error: ", cmd.String(), out)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Closes the tunnel interface
|
||||
func Close() error {
|
||||
name, err := tunIface.Name()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sockPath := "/var/run/wireguard/" + name + ".sock"
|
||||
|
||||
err = CloseWithUserspace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(sockPath); err == nil {
|
||||
err = os.Remove(sockPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
routeCmd := exec.Command("route", "add", "-net", w.Address.Network.String(), "-interface", w.Name)
|
||||
if out, err := routeCmd.CombinedOutput(); err != nil {
|
||||
log.Printf("adding route command \"%v\" failed with output %s and error: ", routeCmd.String(), out)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,37 +1,58 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"os"
|
||||
)
|
||||
|
||||
type NativeLink struct {
|
||||
Link *netlink.Link
|
||||
}
|
||||
|
||||
func WireguardModExists() bool {
|
||||
link := newWGLink("mustnotexist")
|
||||
|
||||
// We willingly try to create a device with an invalid
|
||||
// MTU here as the validation of the MTU will be performed after
|
||||
// the validation of the link kind and hence allows us to check
|
||||
// for the existance of the wireguard module without actually
|
||||
// creating a link.
|
||||
//
|
||||
// As a side-effect, this will also let the kernel lazy-load
|
||||
// the wireguard module.
|
||||
link.attrs.MTU = math.MaxInt
|
||||
|
||||
err := netlink.LinkAdd(link)
|
||||
|
||||
return errors.Is(err, syscall.EINVAL)
|
||||
}
|
||||
|
||||
// Create Creates a new Wireguard interface, sets a given IP and brings it up.
|
||||
// Will reuse an existing one.
|
||||
func Create(iface string, address string) error {
|
||||
func (w *WGIface) Create() error {
|
||||
|
||||
if WireguardModExists() {
|
||||
log.Debug("using kernel Wireguard module")
|
||||
return CreateWithKernel(iface, address)
|
||||
return w.CreateWithKernel()
|
||||
} else {
|
||||
return CreateWithUserspace(iface, address)
|
||||
return w.CreateWithUserspace()
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWithKernel Creates a new Wireguard interface using kernel Wireguard module.
|
||||
// Works for Linux and offers much better network performance
|
||||
func CreateWithKernel(iface string, address string) error {
|
||||
attrs := netlink.NewLinkAttrs()
|
||||
attrs.Name = iface
|
||||
func (w *WGIface) CreateWithKernel() error {
|
||||
|
||||
link := wgLink{
|
||||
attrs: &attrs,
|
||||
}
|
||||
link := newWGLink(w.Name)
|
||||
|
||||
// check if interface exists
|
||||
l, err := netlink.LinkByName(iface)
|
||||
l, err := netlink.LinkByName(w.Name)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case netlink.LinkNotFoundError:
|
||||
@@ -43,37 +64,39 @@ func CreateWithKernel(iface string, address string) error {
|
||||
|
||||
// remove if interface exists
|
||||
if l != nil {
|
||||
err = netlink.LinkDel(&link)
|
||||
err = netlink.LinkDel(link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("adding device: %s", iface)
|
||||
err = netlink.LinkAdd(&link)
|
||||
log.Debugf("adding device: %s", w.Name)
|
||||
err = netlink.LinkAdd(link)
|
||||
if os.IsExist(err) {
|
||||
log.Infof("interface %s already exists. Will reuse.", iface)
|
||||
log.Infof("interface %s already exists. Will reuse.", w.Name)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = assignAddr(address, iface)
|
||||
w.Interface = link
|
||||
|
||||
err = w.assignAddr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// todo do a discovery
|
||||
log.Debugf("setting MTU: %d interface: %s", defaultMTU, iface)
|
||||
err = netlink.LinkSetMTU(&link, defaultMTU)
|
||||
log.Debugf("setting MTU: %d interface: %s", w.MTU, w.Name)
|
||||
err = netlink.LinkSetMTU(link, w.MTU)
|
||||
if err != nil {
|
||||
log.Errorf("error setting MTU on interface: %s", iface)
|
||||
log.Errorf("error setting MTU on interface: %s", w.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("bringing up interface: %s", iface)
|
||||
err = netlink.LinkSetUp(&link)
|
||||
log.Debugf("bringing up interface: %s", w.Name)
|
||||
err = netlink.LinkSetUp(link)
|
||||
if err != nil {
|
||||
log.Errorf("error bringing up interface: %s", iface)
|
||||
log.Errorf("error bringing up interface: %s", w.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -81,39 +104,37 @@ func CreateWithKernel(iface string, address string) error {
|
||||
}
|
||||
|
||||
// assignAddr Adds IP address to the tunnel interface
|
||||
func assignAddr(address, name string) error {
|
||||
var err error
|
||||
attrs := netlink.NewLinkAttrs()
|
||||
attrs.Name = name
|
||||
func (w *WGIface) assignAddr() error {
|
||||
|
||||
link := wgLink{
|
||||
attrs: &attrs,
|
||||
}
|
||||
mask, _ := w.Address.Network.Mask.Size()
|
||||
address := fmt.Sprintf("%s/%d", w.Address.IP.String(), mask)
|
||||
|
||||
link := newWGLink(w.Name)
|
||||
|
||||
//delete existing addresses
|
||||
list, err := netlink.AddrList(&link, 0)
|
||||
list, err := netlink.AddrList(link, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(list) > 0 {
|
||||
for _, a := range list {
|
||||
err = netlink.AddrDel(&link, &a)
|
||||
err = netlink.AddrDel(link, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("adding address %s to interface: %s", address, attrs.Name)
|
||||
log.Debugf("adding address %s to interface: %s", address, w.Name)
|
||||
addr, _ := netlink.ParseAddr(address)
|
||||
err = netlink.AddrAdd(&link, addr)
|
||||
err = netlink.AddrAdd(link, addr)
|
||||
if os.IsExist(err) {
|
||||
log.Infof("interface %s already has the address: %s", attrs.Name, address)
|
||||
log.Infof("interface %s already has the address: %s", w.Name, address)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
// On linux, the link must be brought up
|
||||
err = netlink.LinkSetUp(&link)
|
||||
err = netlink.LinkSetUp(link)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -121,48 +142,26 @@ type wgLink struct {
|
||||
attrs *netlink.LinkAttrs
|
||||
}
|
||||
|
||||
func newWGLink(name string) *wgLink {
|
||||
attrs := netlink.NewLinkAttrs()
|
||||
attrs.Name = name
|
||||
|
||||
return &wgLink{
|
||||
attrs: &attrs,
|
||||
}
|
||||
}
|
||||
|
||||
// Attrs returns the Wireguard's default attributes
|
||||
func (w *wgLink) Attrs() *netlink.LinkAttrs {
|
||||
return w.attrs
|
||||
func (l *wgLink) Attrs() *netlink.LinkAttrs {
|
||||
return l.attrs
|
||||
}
|
||||
|
||||
// Type returns the interface type
|
||||
func (w *wgLink) Type() string {
|
||||
func (l *wgLink) Type() string {
|
||||
return "wireguard"
|
||||
}
|
||||
|
||||
// Closes the tunnel interface
|
||||
func Close() error {
|
||||
|
||||
if tunIface != nil {
|
||||
return CloseWithUserspace()
|
||||
} else {
|
||||
var iface = ""
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wg.Close()
|
||||
devList, err := wg.Devices()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, wgDev := range devList {
|
||||
// todo check after move the WgPort constant to the client
|
||||
if wgDev.ListenPort == WgPort {
|
||||
iface = wgDev.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
if iface == "" {
|
||||
return fmt.Errorf("Wireguard Interface not found")
|
||||
}
|
||||
attrs := netlink.NewLinkAttrs()
|
||||
attrs.Name = iface
|
||||
|
||||
link := wgLink{
|
||||
attrs: &attrs,
|
||||
}
|
||||
return netlink.LinkDel(&link)
|
||||
}
|
||||
// Close deletes the link interface
|
||||
func (l *wgLink) Close() error {
|
||||
return netlink.LinkDel(l)
|
||||
}
|
||||
|
||||
@@ -12,24 +12,36 @@ import (
|
||||
|
||||
// keep darwin compability
|
||||
const (
|
||||
key = "0PMI6OkB5JmB+Jj/iWWHekuQRx+bipZirWCWKFXexHc="
|
||||
peerPubKey = "Ok0mC0qlJyXEPKh2UFIpsI2jG0L7LRpC3sLAusSJ5CQ="
|
||||
WgIntNumber = 2000
|
||||
)
|
||||
|
||||
var (
|
||||
key string
|
||||
peerPubKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
privateKey, _ := wgtypes.GeneratePrivateKey()
|
||||
key = privateKey.String()
|
||||
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
|
||||
peerPubKey = peerPrivateKey.PublicKey().String()
|
||||
}
|
||||
|
||||
//
|
||||
func Test_CreateInterface(t *testing.T) {
|
||||
ifaceName := "utun999"
|
||||
wgIP := "10.99.99.1/24"
|
||||
err := Create(ifaceName, wgIP)
|
||||
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+1)
|
||||
wgIP := "10.99.99.1/32"
|
||||
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = Close()
|
||||
err = iface.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -44,29 +56,59 @@ func Test_CreateInterface(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
d, err := wg.Device(ifaceName)
|
||||
func Test_Close(t *testing.T) {
|
||||
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+2)
|
||||
wgIP := "10.99.99.2/32"
|
||||
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// todo move the WgPort constant to the client
|
||||
WgPort = d.ListenPort
|
||||
}
|
||||
func Test_ConfigureInterface(t *testing.T) {
|
||||
ifaceName := "utun1000"
|
||||
wgIP := "10.99.99.10/24"
|
||||
err := Create(ifaceName, wgIP)
|
||||
err = iface.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = Close()
|
||||
err = wg.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = Configure(ifaceName, key)
|
||||
err = iface.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ConfigureInterface(t *testing.T) {
|
||||
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+3)
|
||||
wgIP := "10.99.99.5/30"
|
||||
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = iface.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
port, err := iface.GetListenPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Configure(key, *port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -92,30 +134,41 @@ func Test_ConfigureInterface(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_UpdatePeer(t *testing.T) {
|
||||
ifaceName := "utun1001"
|
||||
wgIP := "10.99.99.20/24"
|
||||
err := Create(ifaceName, wgIP)
|
||||
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+4)
|
||||
wgIP := "10.99.99.9/30"
|
||||
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = Close()
|
||||
err = iface.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
err = Configure(ifaceName, key)
|
||||
port, err := iface.GetListenPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Configure(key, *port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keepAlive := 15 * time.Second
|
||||
allowedIP := "10.99.99.2/32"
|
||||
endpoint := "127.0.0.1:9900"
|
||||
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
|
||||
allowedIP := "10.99.99.10/32"
|
||||
endpoint, err := net.ResolveUDPAddr("udp", "127.0.0.1:9900")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer, err := getPeer(ifaceName, t)
|
||||
err = iface.UpdatePeer(peerPubKey, allowedIP, keepAlive, endpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer, err := getPeer(ifaceName, peerPubKey, t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -123,11 +176,7 @@ func Test_UpdatePeer(t *testing.T) {
|
||||
t.Fatal("configured peer with mismatched keepalive interval value")
|
||||
}
|
||||
|
||||
resolvedEndpoint, err := net.ResolveUDPAddr("udp", endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if peer.Endpoint.String() != resolvedEndpoint.String() {
|
||||
if peer.Endpoint.String() != endpoint.String() {
|
||||
t.Fatal("configured peer with mismatched endpoint")
|
||||
}
|
||||
|
||||
@@ -143,110 +192,144 @@ func Test_UpdatePeer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UpdatePeerEndpoint(t *testing.T) {
|
||||
ifaceName := "utun1002"
|
||||
wgIP := "10.99.99.30/24"
|
||||
err := Create(ifaceName, wgIP)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
err = Configure(ifaceName, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keepAlive := 15 * time.Second
|
||||
allowedIP := "10.99.99.2/32"
|
||||
endpoint := "127.0.0.1:9900"
|
||||
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newEndpoint := "127.0.0.1:9999"
|
||||
err = UpdatePeerEndpoint(ifaceName, peerPubKey, newEndpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
peer, err := getPeer(ifaceName, t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if peer.Endpoint.String() != newEndpoint {
|
||||
t.Fatal("configured peer with mismatched endpoint")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RemovePeer(t *testing.T) {
|
||||
ifaceName := "utun1003"
|
||||
wgIP := "10.99.99.40/24"
|
||||
err := Create(ifaceName, wgIP)
|
||||
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+4)
|
||||
wgIP := "10.99.99.13/30"
|
||||
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = Close()
|
||||
err = iface.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
err = Configure(ifaceName, key)
|
||||
port, err := iface.GetListenPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Configure(key, *port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keepAlive := 15 * time.Second
|
||||
allowedIP := "10.99.99.2/32"
|
||||
endpoint := "127.0.0.1:9900"
|
||||
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
|
||||
allowedIP := "10.99.99.14/32"
|
||||
|
||||
err = iface.UpdatePeer(peerPubKey, allowedIP, keepAlive, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = RemovePeer(ifaceName, peerPubKey)
|
||||
err = iface.RemovePeer(peerPubKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = getPeer(ifaceName, t)
|
||||
_, err = getPeer(ifaceName, peerPubKey, t)
|
||||
if err.Error() != "peer not found" {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
func Test_Close(t *testing.T) {
|
||||
ifaceName := "utun1004"
|
||||
wgIP := "10.99.99.50/24"
|
||||
err := Create(ifaceName, wgIP)
|
||||
|
||||
func Test_ConnectPeers(t *testing.T) {
|
||||
peer1ifaceName := fmt.Sprintf("utun%d", WgIntNumber+400)
|
||||
peer1wgIP := "10.99.99.17/30"
|
||||
peer1Key, _ := wgtypes.GeneratePrivateKey()
|
||||
//peer1Port := WgPort + 4
|
||||
|
||||
peer2ifaceName := fmt.Sprintf("utun%d", 500)
|
||||
peer2wgIP := "10.99.99.18/30"
|
||||
peer2Key, _ := wgtypes.GeneratePrivateKey()
|
||||
//peer2Port := WgPort + 5
|
||||
|
||||
keepAlive := 1 * time.Second
|
||||
|
||||
iface1, err := NewWGIface(peer1ifaceName, peer1wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wg, err := wgctrl.New()
|
||||
err = iface1.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer1Port, err := iface1.GetListenPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer1endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", *peer1Port))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
iface2, err := NewWGIface(peer2ifaceName, peer2wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface2.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer2Port, err := iface2.GetListenPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer2endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", *peer2Port))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = wg.Close()
|
||||
err = iface1.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = iface2.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
d, err := wg.Device(ifaceName)
|
||||
err = iface1.Configure(peer1Key.String(), *peer1Port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// todo move the WgPort constant to the client
|
||||
WgPort = d.ListenPort
|
||||
err = Close()
|
||||
err = iface2.Configure(peer2Key.String(), *peer2Port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = iface1.UpdatePeer(peer2Key.PublicKey().String(), peer2wgIP, keepAlive, peer2endpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface2.UpdatePeer(peer1Key.PublicKey().String(), peer1wgIP, keepAlive, peer1endpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
timeout := 10 * time.Second
|
||||
timeoutChannel := time.After(timeout)
|
||||
for {
|
||||
select {
|
||||
case <-timeoutChannel:
|
||||
t.Fatalf("waiting for peer handshake timeout after %s", timeout.String())
|
||||
default:
|
||||
}
|
||||
peer, gpErr := getPeer(peer1ifaceName, peer2Key.PublicKey().String(), t)
|
||||
if gpErr != nil {
|
||||
t.Fatal(gpErr)
|
||||
}
|
||||
if !peer.LastHandshakeTime.IsZero() {
|
||||
t.Log("peers successfully handshake")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func getPeer(ifaceName string, t *testing.T) (wgtypes.Peer, error) {
|
||||
|
||||
func getPeer(ifaceName, peerPubKey string, t *testing.T) (wgtypes.Peer, error) {
|
||||
emptyPeer := wgtypes.Peer{}
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,12 +1,58 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package iface
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"net"
|
||||
)
|
||||
|
||||
// CreateWithUserspace Creates a new Wireguard interface, using wireguard-go userspace implementation
|
||||
func (w *WGIface) CreateWithUserspace() error {
|
||||
|
||||
tunIface, err := tun.CreateTUN(w.Name, w.MTU)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Interface = tunIface
|
||||
|
||||
// We need to create a wireguard-go device and listen to configuration requests
|
||||
tunDevice := device.NewDevice(tunIface, conn.NewDefaultBind(), device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
|
||||
err = tunDevice.Up()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uapi, err := getUAPI(w.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
uapiConn, uapiErr := uapi.Accept()
|
||||
if uapiErr != nil {
|
||||
log.Traceln("uapi Accept failed with error: ", uapiErr)
|
||||
continue
|
||||
}
|
||||
go tunDevice.IpcHandle(uapiConn)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debugln("UAPI listener started")
|
||||
|
||||
err = w.assignAddr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getUAPI returns a Listener
|
||||
func getUAPI(iface string) (net.Listener, error) {
|
||||
tunSock, err := ipc.UAPIOpen(iface)
|
||||
|
||||
@@ -1,46 +1,47 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/windows/driver"
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Create Creates a new Wireguard interface, sets a given IP and brings it up.
|
||||
func Create(iface string, address string) error {
|
||||
return CreateWithUserspace(iface, address)
|
||||
func (w *WGIface) Create() error {
|
||||
|
||||
WintunStaticRequestedGUID, _ := windows.GenerateGUID()
|
||||
adapter, err := driver.CreateAdapter(w.Name, "WireGuard", &WintunStaticRequestedGUID)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error creating adapter: %w", err)
|
||||
return err
|
||||
}
|
||||
w.Interface = adapter
|
||||
luid := adapter.LUID()
|
||||
err = adapter.SetLogging(driver.AdapterLogOn)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error enabling adapter logging: %w", err)
|
||||
return err
|
||||
}
|
||||
err = adapter.SetAdapterState(driver.AdapterStateUp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, _ := luid.GUID()
|
||||
log.Debugln("device guid: ", state.String())
|
||||
return w.assignAddr(luid)
|
||||
}
|
||||
|
||||
// assignAddr Adds IP address to the tunnel interface and network route based on the range provided
|
||||
func assignAddr(address string, ifaceName string) error {
|
||||
func (w *WGIface) assignAddr(luid winipcfg.LUID) error {
|
||||
|
||||
nativeTunDevice := tunIface.(*tun.NativeTun)
|
||||
luid := winipcfg.LUID(nativeTunDevice.LUID())
|
||||
|
||||
ip, ipnet, _ := net.ParseCIDR(address)
|
||||
|
||||
log.Debugf("adding address %s to interface: %s", address, ifaceName)
|
||||
err := luid.SetIPAddresses([]net.IPNet{{ip, ipnet.Mask}})
|
||||
log.Debugf("adding address %s to interface: %s", w.Address.IP, w.Name)
|
||||
err := luid.SetIPAddresses([]net.IPNet{{w.Address.IP, w.Address.Network.Mask}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("adding Routes to interface: %s", ifaceName)
|
||||
err = luid.SetRoutes([]*winipcfg.RouteData{{*ipnet, ipnet.IP, 0}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getUAPI returns a Listener
|
||||
func getUAPI(iface string) (net.Listener, error) {
|
||||
return ipc.UAPIListen(iface)
|
||||
}
|
||||
|
||||
// Closes the tunnel interface
|
||||
func Close() error {
|
||||
return CloseWithUserspace()
|
||||
}
|
||||
|
||||
144
iface/mod.go
@@ -1,144 +0,0 @@
|
||||
// +build linux
|
||||
|
||||
package iface
|
||||
|
||||
// Holds logic to check existence of Wireguard kernel module
|
||||
// Copied from https://github.com/paultag/go-modprobe
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"fmt"
|
||||
"golang.org/x/sys/unix"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// get the root directory for the kernel modules. If this line panics,
|
||||
// it's because getModuleRoot has failed to get the uname of the running
|
||||
// kernel (likely a non-POSIX system, but maybe a broken kernel?)
|
||||
moduleRoot = getModuleRoot()
|
||||
)
|
||||
|
||||
// Get the module root (/lib/modules/$(uname -r)/)
|
||||
func getModuleRoot() string {
|
||||
uname := unix.Utsname{}
|
||||
if err := unix.Uname(&uname); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
i := 0
|
||||
for ; uname.Release[i] != 0; i++ {
|
||||
}
|
||||
|
||||
return filepath.Join(
|
||||
"/lib/modules",
|
||||
string(uname.Release[:i]),
|
||||
)
|
||||
}
|
||||
|
||||
// modName will, given a file descriptor to a Kernel Module (.ko file), parse the
|
||||
// binary to get the module name. For instance, given a handle to the file at
|
||||
// `kernel/drivers/usb/gadget/legacy/g_ether.ko`, return `g_ether`.
|
||||
func modName(file *os.File) (string, error) {
|
||||
f, err := elf.NewFile(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
syms, err := f.Symbols()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, sym := range syms {
|
||||
if strings.Compare(sym.Name, "__this_module") == 0 {
|
||||
section := f.Sections[sym.Section]
|
||||
data, err := section.Data()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(data) < 25 {
|
||||
return "", fmt.Errorf("modprobe: data is short, __this_module is '%s'", data)
|
||||
}
|
||||
|
||||
data = data[24:]
|
||||
i := 0
|
||||
for ; data[i] != 0x00; i++ {
|
||||
}
|
||||
return string(data[:i]), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("No name found. Is this a .ko or just an ELF?")
|
||||
}
|
||||
|
||||
// Open every single kernel module under the root, and parse the ELF headers to
|
||||
// extract the module name.
|
||||
func elfMap(root string) (map[string]string, error) {
|
||||
ret := map[string]string{}
|
||||
|
||||
err := filepath.Walk(
|
||||
root,
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
|
||||
if err != nil {
|
||||
// skip broken files
|
||||
return nil
|
||||
}
|
||||
|
||||
if !info.Mode().IsRegular() {
|
||||
return nil
|
||||
}
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
name, err := modName(fd)
|
||||
if err != nil {
|
||||
/* For now, let's just ignore that and avoid adding to it */
|
||||
return nil
|
||||
}
|
||||
|
||||
ret[name] = path
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Open every single kernel module under the kernel module directory
|
||||
// (/lib/modules/$(uname -r)/), and parse the ELF headers to extract the
|
||||
// module name.
|
||||
func generateMap() (map[string]string, error) {
|
||||
return elfMap(moduleRoot)
|
||||
}
|
||||
|
||||
// WireguardModExists returns true if Wireguard kernel module exists.
|
||||
func WireguardModExists() bool {
|
||||
_, err := resolveModName("wireguard")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// resolveModName will, given a module name (such as `wireguard`) return an absolute
|
||||
// path to the .ko that provides that module.
|
||||
func resolveModName(name string) (string, error) {
|
||||
paths, err := generateMap()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fsPath := paths[name]
|
||||
if !strings.HasPrefix(fsPath, moduleRoot) {
|
||||
return "", fmt.Errorf("module isn't in the module directory")
|
||||
}
|
||||
|
||||
return fsPath, nil
|
||||
}
|
||||
7
infrastructure_files/configure.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
unset $(grep -v '^#' ./setup.env | sed -E 's/(.*)=.*/\1/' | xargs)
|
||||
export $(grep -v '^#' ./setup.env | xargs)
|
||||
|
||||
envsubst < docker-compose.yml.tmpl > docker-compose.yml
|
||||
envsubst < management.json.tmpl > management.json
|
||||
@@ -1,56 +0,0 @@
|
||||
version: "3"
|
||||
services:
|
||||
#UI dashboard
|
||||
dashboard:
|
||||
image: wiretrustee/dashboard:main
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 80:80
|
||||
# - 443:443
|
||||
environment:
|
||||
- AUTH0_DOMAIN=<YOUR AUTH0 DOMAIN>
|
||||
- AUTH0_CLIENT_ID=<YOUR AUTH0 CLIENT ID>
|
||||
- AUTH0_AUDIENCE=<YOUR AUTH0 AUDIENCE>
|
||||
- WIRETRUSTEE_MGMT_API_ENDPOINT=http://localhost:33071
|
||||
# - NGINX_SSL_PORT: 443
|
||||
# - LETSENCRYPT_DOMAIN: <YOUR DOMAIN>
|
||||
# - LETSENCRYPT_EMAIL: <YOUR EMAIL>
|
||||
# Signal
|
||||
signal:
|
||||
image: wiretrustee/signal:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- wiretrustee-mgmt:/var/lib/wiretrustee
|
||||
- /varl/log/wiretrustee/signal.log:/var/log/wiretrustee/signal.log
|
||||
ports:
|
||||
- 10000:10000
|
||||
# # port and command for Let's Encrypt validation
|
||||
# - 443:443
|
||||
# command: ["--letsencrypt-domain", "<YOUR-DOMAIN>", "--log-file", "console"]
|
||||
# Management
|
||||
management:
|
||||
image: wiretrustee/management:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- wiretrustee-mgmt:/var/lib/wiretrustee
|
||||
- ./management.json:/etc/wiretrustee/management.json
|
||||
# - /var/log/wiretrustee/management.log:/var/log/wiretrustee/management.log
|
||||
ports:
|
||||
- 33073:33073 #gRPC port
|
||||
- 33071:33071 #HTTP port
|
||||
# # port and command for Let's Encrypt validation
|
||||
# - 443:443
|
||||
# command: ["--letsencrypt-domain", "<YOUR-DOMAIN>", "--log-file", "console"]
|
||||
# Coturn
|
||||
coturn:
|
||||
image: coturn/coturn
|
||||
restart: unless-stopped
|
||||
domainname: stun.wiretrustee.com
|
||||
volumes:
|
||||
- ./turnserver.conf:/etc/turnserver.conf:ro
|
||||
# - ./privkey.pem:/etc/coturn/private/privkey.pem:ro
|
||||
# - ./cert.pem:/etc/coturn/certs/cert.pem:ro
|
||||
network_mode: host
|
||||
volumes:
|
||||
wiretrustee-mgmt:
|
||||
wiretrustee-signal:
|
||||
61
infrastructure_files/docker-compose.yml.tmpl
Normal file
@@ -0,0 +1,61 @@
|
||||
version: "3"
|
||||
services:
|
||||
#UI dashboard
|
||||
dashboard:
|
||||
image: wiretrustee/dashboard:main
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
environment:
|
||||
- AUTH0_DOMAIN=$WIRETRUSTEE_AUTH0_DOMAIN
|
||||
- AUTH0_CLIENT_ID=$WIRETRUSTEE_AUTH0_CLIENT_ID
|
||||
- AUTH0_AUDIENCE=$WIRETRUSTEE_AUTH0_AUDIENCE
|
||||
- WIRETRUSTEE_MGMT_API_ENDPOINT=https://$WIRETRUSTEE_DOMAIN:33071
|
||||
- NGINX_SSL_PORT=443
|
||||
- LETSENCRYPT_DOMAIN=$WIRETRUSTEE_DOMAIN
|
||||
- LETSENCRYPT_EMAIL=$WIRETRUSTEE_LETSENCRYPT_EMAIL
|
||||
volumes:
|
||||
- /var/lib/wiretrustee/dashboard/letsencrypt:/etc/letsencrypt/
|
||||
# Signal
|
||||
signal:
|
||||
image: wiretrustee/signal:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- wiretrustee-signal:/var/lib/wiretrustee
|
||||
# - /var/log/wiretrustee/signal.log:/var/log/wiretrustee/signal.log
|
||||
ports:
|
||||
- 10000:10000
|
||||
# # port and command for Let's Encrypt validation
|
||||
# - 443:443
|
||||
# command: ["--letsencrypt-domain", "$WIRETRUSTEE_DOMAIN", "--log-file", "console"]
|
||||
# Management
|
||||
management:
|
||||
image: wiretrustee/management:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- dashboard
|
||||
volumes:
|
||||
- wiretrustee-mgmt:/var/lib/wiretrustee
|
||||
- /var/lib/wiretrustee/dashboard/letsencrypt:/etc/letsencrypt:ro
|
||||
- ./management.json:/etc/wiretrustee/management.json
|
||||
# - /var/log/wiretrustee/management.log:/var/log/wiretrustee/management.log
|
||||
ports:
|
||||
- 33073:33073 #gRPC port
|
||||
- 33071:33071 #HTTP port
|
||||
# # port and command for Let's Encrypt validation
|
||||
# - 443:443
|
||||
# command: ["--letsencrypt-domain", "$WIRETRUSTEE_DOMAIN", "--log-file", "console"]
|
||||
# Coturn
|
||||
coturn:
|
||||
image: coturn/coturn
|
||||
restart: unless-stopped
|
||||
domainname: <YOUR DOMAIN>
|
||||
volumes:
|
||||
- ./turnserver.conf:/etc/turnserver.conf:ro
|
||||
# - ./privkey.pem:/etc/coturn/private/privkey.pem:ro
|
||||
# - ./cert.pem:/etc/coturn/certs/cert.pem:ro
|
||||
network_mode: host
|
||||
volumes:
|
||||
wiretrustee-mgmt:
|
||||
wiretrustee-signal:
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"Stuns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "stun:stun.wiretrustee.com:3468",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
}
|
||||
],
|
||||
"TURNConfig": {
|
||||
"Turns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "turn:stun.wiretrustee.com:3468",
|
||||
"Username": "some_user",
|
||||
"Password": "c29tZV9wYXNzd29yZA=="
|
||||
}
|
||||
],
|
||||
"CredentialsTTL": "1h",
|
||||
"Secret": "c29tZV9wYXNzd29yZA==",
|
||||
"TimeBasedCredentials": true
|
||||
},
|
||||
"Signal": {
|
||||
"Proto": "http",
|
||||
"URI": "signal.wiretrustee.com:10000",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
},
|
||||
"Datadir": "",
|
||||
"HttpConfig": {
|
||||
"LetsEncryptDomain": "<PASTE YOUR LET'S ENCRYPT DOMAIN HERE>",
|
||||
"Address": "0.0.0.0:33071",
|
||||
"AuthIssuer": "<PASTE YOUR AUTH0 ISSUER HERE>,",
|
||||
"AuthAudience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
|
||||
"AuthKeysLocation": "<PASTE YOUR AUTH0 PUBLIC JWT KEYS LOCATION HERE>"
|
||||
}
|
||||
}
|
||||
46
infrastructure_files/management.json.tmpl
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Stuns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "stun:$WIRETRUSTEE_DOMAIN:3478",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
}
|
||||
],
|
||||
"TURNConfig": {
|
||||
"Turns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "turn:$WIRETRUSTEE_DOMAIN:3478",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
}
|
||||
],
|
||||
"CredentialsTTL": "12h",
|
||||
"Secret": "secret",
|
||||
"TimeBasedCredentials": false
|
||||
},
|
||||
"Signal": {
|
||||
"Proto": "http",
|
||||
"URI": "$WIRETRUSTEE_DOMAIN:10000",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
},
|
||||
"Datadir": "",
|
||||
"HttpConfig": {
|
||||
"Address": "0.0.0.0:33071",
|
||||
"AuthIssuer": "https://$WIRETRUSTEE_AUTH0_DOMAIN/",
|
||||
"AuthAudience": "$WIRETRUSTEE_AUTH0_AUDIENCE",
|
||||
"AuthKeysLocation": "https://$WIRETRUSTEE_AUTH0_DOMAIN/.well-known/jwks.json"
|
||||
},
|
||||
"IdpManagerConfig": {
|
||||
"Manager": "none",
|
||||
"Auth0ClientCredentials": {
|
||||
"Audience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
|
||||
"AuthIssuer": "<PASTE YOUR AUTH0 Auth Issuer HERE>",
|
||||
"ClientId": "<PASTE YOUR AUTH0 Application Client ID HERE>",
|
||||
"ClientSecret": "<PASTE YOUR AUTH0 Application Client Secret HERE>",
|
||||
"GrantType": "client_credentials"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
infrastructure_files/setup.env
Normal file
@@ -0,0 +1,10 @@
|
||||
# e.g. app.mydomain.com
|
||||
WIRETRUSTEE_DOMAIN=""
|
||||
# e.g. dev-24vkclam.us.auth0.com
|
||||
WIRETRUSTEE_AUTH0_DOMAIN=""
|
||||
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
|
||||
WIRETRUSTEE_AUTH0_CLIENT_ID=""
|
||||
# e.g. https://app.mydomain.com/
|
||||
WIRETRUSTEE_AUTH0_AUDIENCE=""
|
||||
# e.g. hello@mydomain.com
|
||||
WIRETRUSTEE_LETSENCRYPT_EMAIL=""
|
||||
@@ -14,7 +14,8 @@ Flags:
|
||||
-h, --help help for management
|
||||
--letsencrypt-domain string 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
|
||||
--port int server port to listen on (default 33073)
|
||||
|
||||
--cert-file string Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect
|
||||
--cert-key string Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect
|
||||
Global Flags:
|
||||
--config string Wiretrustee config file location to write new config to (default "/etc/wiretrustee/config.json")
|
||||
--log-level string (default "info")
|
||||
@@ -44,7 +45,7 @@ docker run -d --name wiretrustee-management \
|
||||
wiretrustee/management:latest \
|
||||
--letsencrypt-domain <YOUR-DOMAIN>
|
||||
```
|
||||
> An example of config.json can be found here [config.json](../infrastructure_files/config.json)
|
||||
> An example of config.json can be found here [management.json](../infrastructure_files/management.json.tmpl)
|
||||
|
||||
Trigger Let's encrypt certificate generation:
|
||||
```bash
|
||||
|
||||
@@ -1,233 +1,17 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/client/system"
|
||||
"github.com/wiretrustee/wiretrustee/encryption"
|
||||
"github.com/wiretrustee/wiretrustee/management/proto"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
key wgtypes.Key
|
||||
realClient proto.ManagementServiceClient
|
||||
ctx context.Context
|
||||
conn *grpc.ClientConn
|
||||
}
|
||||
|
||||
// NewClient creates a new client to Management service
|
||||
func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*Client, error) {
|
||||
|
||||
transportOption := grpc.WithInsecure()
|
||||
|
||||
if tlsEnabled {
|
||||
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
||||
}
|
||||
|
||||
mgmCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
conn, err := grpc.DialContext(
|
||||
mgmCtx,
|
||||
addr,
|
||||
transportOption,
|
||||
grpc.WithBlock(),
|
||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: 3 * time.Second,
|
||||
Timeout: 2 * time.Second,
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("failed creating connection to Management Srvice %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
realClient := proto.NewManagementServiceClient(conn)
|
||||
|
||||
return &Client{
|
||||
key: ourPrivateKey,
|
||||
realClient: realClient,
|
||||
ctx: ctx,
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close closes connection to the Management Service
|
||||
func (c *Client) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
//defaultBackoff is a basic backoff mechanism for general issues
|
||||
func defaultBackoff() backoff.BackOff {
|
||||
return &backoff.ExponentialBackOff{
|
||||
InitialInterval: 800 * time.Millisecond,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 30 * time.Second,
|
||||
MaxElapsedTime: 24 * 3 * time.Hour, //stop after 3 days trying
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
}
|
||||
|
||||
// Sync wraps the real client's Sync endpoint call and takes care of retries and encryption/decryption of messages
|
||||
// Blocking request. The result will be sent via msgHandler callback function
|
||||
func (c *Client) Sync(msgHandler func(msg *proto.SyncResponse) error) error {
|
||||
|
||||
var backOff = defaultBackoff()
|
||||
|
||||
operation := func() error {
|
||||
|
||||
// todo we already have it since we did the Login, maybe cache it locally?
|
||||
serverPubKey, err := c.GetServerPublicKey()
|
||||
if err != nil {
|
||||
log.Errorf("failed getting Management Service public key: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
stream, err := c.connectToStream(*serverPubKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to open Management Service stream: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("connected to the Management Service Stream")
|
||||
|
||||
// blocking until error
|
||||
err = c.receiveEvents(stream, *serverPubKey, msgHandler)
|
||||
if err != nil {
|
||||
/*if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.PermissionDenied {
|
||||
//todo handle differently??
|
||||
}*/
|
||||
return err
|
||||
}
|
||||
backOff.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, backOff)
|
||||
if err != nil {
|
||||
log.Errorf("exiting Management Service connection retry loop due to unrecoverable error %s ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) connectToStream(serverPubKey wgtypes.Key) (proto.ManagementService_SyncClient, error) {
|
||||
req := &proto.SyncRequest{}
|
||||
|
||||
myPrivateKey := c.key
|
||||
myPublicKey := myPrivateKey.PublicKey()
|
||||
|
||||
encryptedReq, err := encryption.EncryptMessage(serverPubKey, myPrivateKey, req)
|
||||
if err != nil {
|
||||
log.Errorf("failed encrypting message: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
syncReq := &proto.EncryptedMessage{WgPubKey: myPublicKey.String(), Body: encryptedReq}
|
||||
return c.realClient.Sync(c.ctx, syncReq)
|
||||
}
|
||||
|
||||
func (c *Client) receiveEvents(stream proto.ManagementService_SyncClient, serverPubKey wgtypes.Key, msgHandler func(msg *proto.SyncResponse) error) error {
|
||||
for {
|
||||
update, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
log.Errorf("managment stream was closed: %s", err)
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("disconnected from Management Service sync stream: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("got an update message from Management Service")
|
||||
decryptedResp := &proto.SyncResponse{}
|
||||
err = encryption.DecryptMessage(serverPubKey, c.key, update.Body, decryptedResp)
|
||||
if err != nil {
|
||||
log.Errorf("failed decrypting update message from Management Service: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = msgHandler(decryptedResp)
|
||||
if err != nil {
|
||||
log.Errorf("failed handling an update message received from Management Service %v", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetServerPublicKey returns server Wireguard public key (used later for encrypting messages sent to the server)
|
||||
func (c *Client) GetServerPublicKey() (*wgtypes.Key, error) {
|
||||
mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second) //todo make a general setting
|
||||
defer cancel()
|
||||
resp, err := c.realClient.GetServerKey(mgmCtx, &proto.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverKey, err := wgtypes.ParseKey(resp.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &serverKey, nil
|
||||
}
|
||||
|
||||
func (c *Client) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*proto.LoginResponse, error) {
|
||||
loginReq, err := encryption.EncryptMessage(serverKey, c.key, req)
|
||||
if err != nil {
|
||||
log.Errorf("failed to encrypt message: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second) //todo make a general setting
|
||||
defer cancel()
|
||||
resp, err := c.realClient.Login(mgmCtx, &proto.EncryptedMessage{
|
||||
WgPubKey: c.key.PublicKey().String(),
|
||||
Body: loginReq,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loginResp := &proto.LoginResponse{}
|
||||
err = encryption.DecryptMessage(serverKey, c.key, resp.Body, loginResp)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt registration message: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return loginResp, nil
|
||||
}
|
||||
|
||||
// Register registers peer on Management Server. It actually calls a Login endpoint with a provided setup key
|
||||
// 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)
|
||||
func (c *Client) Register(serverKey wgtypes.Key, setupKey string) (*proto.LoginResponse, error) {
|
||||
gi := system.GetInfo()
|
||||
meta := &proto.PeerSystemMeta{
|
||||
Hostname: gi.Hostname,
|
||||
GoOS: gi.GoOS,
|
||||
OS: gi.OS,
|
||||
Core: gi.OSVersion,
|
||||
Platform: gi.Platform,
|
||||
Kernel: gi.Kernel,
|
||||
WiretrusteeVersion: "",
|
||||
}
|
||||
log.Debugf("detected system %v", meta)
|
||||
return c.login(serverKey, &proto.LoginRequest{SetupKey: setupKey, Meta: meta})
|
||||
}
|
||||
|
||||
// Login attempts login to Management Server. Takes care of encrypting and decrypting messages.
|
||||
func (c *Client) Login(serverKey wgtypes.Key) (*proto.LoginResponse, error) {
|
||||
return c.login(serverKey, &proto.LoginRequest{})
|
||||
type Client interface {
|
||||
io.Closer
|
||||
Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
||||
GetServerPublicKey() (*wgtypes.Key, error)
|
||||
Register(serverKey wgtypes.Key, setupKey string, info *system.Info) (*proto.LoginResponse, error)
|
||||
Login(serverKey wgtypes.Key) (*proto.LoginResponse, error)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,18 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/client/system"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/wiretrustee/wiretrustee/encryption"
|
||||
"github.com/wiretrustee/wiretrustee/management/proto"
|
||||
mgmtProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
@@ -10,14 +21,12 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var tested *Client
|
||||
var tested *GrpcClient
|
||||
var serverAddr string
|
||||
var mgmtMockServer *mgmt.ManagementServiceServerMock
|
||||
var serverKey wgtypes.Key
|
||||
|
||||
const ValidKey = "A2C8E62B-38F5-4553-B31E-DD66C696CEBB"
|
||||
|
||||
@@ -61,7 +70,7 @@ func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Liste
|
||||
}
|
||||
|
||||
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
||||
accountManager := mgmt.NewManager(store, peersUpdateManager)
|
||||
accountManager := mgmt.NewManager(store, peersUpdateManager, nil)
|
||||
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
||||
if err != nil {
|
||||
@@ -78,6 +87,39 @@ func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Liste
|
||||
return s, lis
|
||||
}
|
||||
|
||||
func startMockManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
lis, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer()
|
||||
|
||||
serverKey, err = wgtypes.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mgmtMockServer = &mgmt.ManagementServiceServerMock{
|
||||
GetServerKeyFunc: func(context.Context, *proto.Empty) (*proto.ServerKeyResponse, error) {
|
||||
response := &proto.ServerKeyResponse{
|
||||
Key: serverKey.PublicKey().String(),
|
||||
}
|
||||
return response, nil
|
||||
},
|
||||
}
|
||||
|
||||
mgmtProto.RegisterManagementServiceServer(s, mgmtMockServer)
|
||||
go func() {
|
||||
if err := s.Serve(lis); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return s, lis
|
||||
}
|
||||
|
||||
func TestClient_GetServerPublicKey(t *testing.T) {
|
||||
|
||||
key, err := tested.GetServerPublicKey()
|
||||
@@ -109,7 +151,8 @@ func TestClient_LoginRegistered(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
resp, err := tested.Register(*key, ValidKey)
|
||||
info := system.GetInfo()
|
||||
resp, err := tested.Register(*key, ValidKey, info)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -125,7 +168,8 @@ func TestClient_Sync(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = tested.Register(*serverKey, ValidKey)
|
||||
info := system.GetInfo()
|
||||
_, err = tested.Register(*serverKey, ValidKey, info)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -139,7 +183,9 @@ func TestClient_Sync(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = remoteClient.Register(*serverKey, ValidKey)
|
||||
|
||||
info = system.GetInfo()
|
||||
_, err = remoteClient.Register(*serverKey, ValidKey, info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -177,3 +223,82 @@ func TestClient_Sync(t *testing.T) {
|
||||
t.Error("timeout waiting for test to finish")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SystemMetaDataFromClient(t *testing.T) {
|
||||
_, lis := startMockManagement(t)
|
||||
|
||||
testKey, err := wgtypes.GenerateKey()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
serverAddr := lis.Addr().String()
|
||||
ctx := context.Background()
|
||||
|
||||
testClient, err := NewClient(ctx, serverAddr, testKey, false)
|
||||
if err != nil {
|
||||
log.Fatalf("error while creating testClient: %v", err)
|
||||
}
|
||||
|
||||
key, err := testClient.GetServerPublicKey()
|
||||
if err != nil {
|
||||
log.Fatalf("error while getting server public key from testclient, %v", err)
|
||||
}
|
||||
|
||||
var actualMeta *proto.PeerSystemMeta
|
||||
var actualValidKey string
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
mgmtMockServer.LoginFunc =
|
||||
func(ctx context.Context, msg *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||
peerKey, err := wgtypes.ParseKey(msg.GetWgPubKey())
|
||||
if err != nil {
|
||||
log.Warnf("error while parsing peer's Wireguard public key %s on Sync request.", msg.WgPubKey)
|
||||
return nil, status.Errorf(codes.InvalidArgument, "provided wgPubKey %s is invalid", msg.WgPubKey)
|
||||
}
|
||||
|
||||
loginReq := &proto.LoginRequest{}
|
||||
err = encryption.DecryptMessage(peerKey, serverKey, msg.Body, loginReq)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
actualMeta = loginReq.GetMeta()
|
||||
actualValidKey = loginReq.GetSetupKey()
|
||||
wg.Done()
|
||||
|
||||
loginResp := &proto.LoginResponse{}
|
||||
encryptedResp, err := encryption.EncryptMessage(peerKey, serverKey, loginResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &mgmtProto.EncryptedMessage{
|
||||
WgPubKey: serverKey.PublicKey().String(),
|
||||
Body: encryptedResp,
|
||||
Version: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
info := system.GetInfo()
|
||||
_, err = testClient.Register(*key, ValidKey, info)
|
||||
if err != nil {
|
||||
t.Errorf("error while trying to register client: %v", err)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
expectedMeta := &proto.PeerSystemMeta{
|
||||
Hostname: info.Hostname,
|
||||
GoOS: info.GoOS,
|
||||
Kernel: info.Kernel,
|
||||
Core: info.OSVersion,
|
||||
Platform: info.Platform,
|
||||
OS: info.OS,
|
||||
WiretrusteeVersion: info.WiretrusteeVersion,
|
||||
}
|
||||
|
||||
assert.Equal(t, ValidKey, actualValidKey)
|
||||
assert.Equal(t, expectedMeta, actualMeta)
|
||||
}
|
||||
|
||||
252
management/client/grpc.go
Normal file
@@ -0,0 +1,252 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/client/system"
|
||||
"github.com/wiretrustee/wiretrustee/encryption"
|
||||
"github.com/wiretrustee/wiretrustee/management/proto"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
type GrpcClient struct {
|
||||
key wgtypes.Key
|
||||
realClient proto.ManagementServiceClient
|
||||
ctx context.Context
|
||||
conn *grpc.ClientConn
|
||||
}
|
||||
|
||||
// NewClient creates a new client to Management service
|
||||
func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*GrpcClient, error) {
|
||||
|
||||
transportOption := grpc.WithTransportCredentials(insecure.NewCredentials())
|
||||
|
||||
if tlsEnabled {
|
||||
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
||||
}
|
||||
|
||||
mgmCtx, cancel := context.WithTimeout(ctx, time.Second*3)
|
||||
defer cancel()
|
||||
conn, err := grpc.DialContext(
|
||||
mgmCtx,
|
||||
addr,
|
||||
transportOption,
|
||||
grpc.WithBlock(),
|
||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: 15 * time.Second,
|
||||
Timeout: 10 * time.Second,
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("failed creating connection to Management Service %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
realClient := proto.NewManagementServiceClient(conn)
|
||||
|
||||
return &GrpcClient{
|
||||
key: ourPrivateKey,
|
||||
realClient: realClient,
|
||||
ctx: ctx,
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close closes connection to the Management Service
|
||||
func (c *GrpcClient) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
//defaultBackoff is a basic backoff mechanism for general issues
|
||||
func defaultBackoff(ctx context.Context) backoff.BackOff {
|
||||
return backoff.WithContext(&backoff.ExponentialBackOff{
|
||||
InitialInterval: 800 * time.Millisecond,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
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
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}, ctx)
|
||||
}
|
||||
|
||||
// ready indicates whether the client is okay and ready to be used
|
||||
// for now it just checks whether gRPC connection to the service is ready
|
||||
func (c *GrpcClient) ready() bool {
|
||||
return c.conn.GetState() == connectivity.Ready || c.conn.GetState() == connectivity.Idle
|
||||
}
|
||||
|
||||
// Sync wraps the real client's Sync endpoint call and takes care of retries and encryption/decryption of messages
|
||||
// Blocking request. The result will be sent via msgHandler callback function
|
||||
func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error {
|
||||
|
||||
var backOff = defaultBackoff(c.ctx)
|
||||
|
||||
operation := func() error {
|
||||
|
||||
log.Debugf("management connection state %v", c.conn.GetState())
|
||||
|
||||
if !c.ready() {
|
||||
return fmt.Errorf("no connection to management")
|
||||
}
|
||||
|
||||
// todo we already have it since we did the Login, maybe cache it locally?
|
||||
serverPubKey, err := c.GetServerPublicKey()
|
||||
if err != nil {
|
||||
log.Errorf("failed getting Management Service public key: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
stream, err := c.connectToStream(*serverPubKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to open Management Service stream: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("connected to the Management Service stream")
|
||||
|
||||
// blocking until error
|
||||
err = c.receiveEvents(stream, *serverPubKey, msgHandler)
|
||||
if err != nil {
|
||||
backOff.Reset()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, backOff)
|
||||
if err != nil {
|
||||
log.Warnf("exiting Management Service connection retry loop due to unrecoverable error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GrpcClient) connectToStream(serverPubKey wgtypes.Key) (proto.ManagementService_SyncClient, error) {
|
||||
req := &proto.SyncRequest{}
|
||||
|
||||
myPrivateKey := c.key
|
||||
myPublicKey := myPrivateKey.PublicKey()
|
||||
|
||||
encryptedReq, err := encryption.EncryptMessage(serverPubKey, myPrivateKey, req)
|
||||
if err != nil {
|
||||
log.Errorf("failed encrypting message: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
syncReq := &proto.EncryptedMessage{WgPubKey: myPublicKey.String(), Body: encryptedReq}
|
||||
return c.realClient.Sync(c.ctx, syncReq)
|
||||
}
|
||||
|
||||
func (c *GrpcClient) receiveEvents(stream proto.ManagementService_SyncClient, serverPubKey wgtypes.Key, msgHandler func(msg *proto.SyncResponse) error) error {
|
||||
for {
|
||||
update, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
log.Errorf("Management stream has been closed by server: %s", err)
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
log.Warnf("disconnected from Management Service sync stream: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("got an update message from Management Service")
|
||||
decryptedResp := &proto.SyncResponse{}
|
||||
err = encryption.DecryptMessage(serverPubKey, c.key, update.Body, decryptedResp)
|
||||
if err != nil {
|
||||
log.Errorf("failed decrypting update message from Management Service: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = msgHandler(decryptedResp)
|
||||
if err != nil {
|
||||
log.Errorf("failed handling an update message received from Management Service: %v", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetServerPublicKey returns server Wireguard public key (used later for encrypting messages sent to the server)
|
||||
func (c *GrpcClient) GetServerPublicKey() (*wgtypes.Key, error) {
|
||||
if !c.ready() {
|
||||
return nil, fmt.Errorf("no connection to management")
|
||||
}
|
||||
|
||||
mgmCtx, cancel := context.WithTimeout(c.ctx, time.Second*2)
|
||||
defer cancel()
|
||||
resp, err := c.realClient.GetServerKey(mgmCtx, &proto.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverKey, err := wgtypes.ParseKey(resp.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &serverKey, nil
|
||||
}
|
||||
|
||||
func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*proto.LoginResponse, error) {
|
||||
if !c.ready() {
|
||||
return nil, fmt.Errorf("no connection to management")
|
||||
}
|
||||
loginReq, err := encryption.EncryptMessage(serverKey, c.key, req)
|
||||
if err != nil {
|
||||
log.Errorf("failed to encrypt message: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
mgmCtx, cancel := context.WithTimeout(c.ctx, time.Second*2)
|
||||
defer cancel()
|
||||
resp, err := c.realClient.Login(mgmCtx, &proto.EncryptedMessage{
|
||||
WgPubKey: c.key.PublicKey().String(),
|
||||
Body: loginReq,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loginResp := &proto.LoginResponse{}
|
||||
err = encryption.DecryptMessage(serverKey, c.key, resp.Body, loginResp)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt registration message: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return loginResp, nil
|
||||
}
|
||||
|
||||
// Register registers peer on Management Server. It actually calls a Login endpoint with a provided setup key
|
||||
// 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)
|
||||
func (c *GrpcClient) Register(serverKey wgtypes.Key, setupKey string, info *system.Info) (*proto.LoginResponse, error) {
|
||||
meta := &proto.PeerSystemMeta{
|
||||
Hostname: info.Hostname,
|
||||
GoOS: info.GoOS,
|
||||
OS: info.OS,
|
||||
Core: info.OSVersion,
|
||||
Platform: info.Platform,
|
||||
Kernel: info.Kernel,
|
||||
WiretrusteeVersion: info.WiretrusteeVersion,
|
||||
}
|
||||
return c.login(serverKey, &proto.LoginRequest{SetupKey: setupKey, Meta: meta})
|
||||
}
|
||||
|
||||
// Login attempts login to Management Server. Takes care of encrypting and decrypting messages.
|
||||
func (c *GrpcClient) Login(serverKey wgtypes.Key) (*proto.LoginResponse, error) {
|
||||
return c.login(serverKey, &proto.LoginRequest{})
|
||||
}
|
||||
50
management/client/mock.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/wiretrustee/wiretrustee/client/system"
|
||||
"github.com/wiretrustee/wiretrustee/management/proto"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
type MockClient struct {
|
||||
CloseFunc func() error
|
||||
SyncFunc func(msgHandler func(msg *proto.SyncResponse) error) error
|
||||
GetServerPublicKeyFunc func() (*wgtypes.Key, error)
|
||||
RegisterFunc func(serverKey wgtypes.Key, setupKey string, info *system.Info) (*proto.LoginResponse, error)
|
||||
LoginFunc func(serverKey wgtypes.Key) (*proto.LoginResponse, error)
|
||||
}
|
||||
|
||||
func (m *MockClient) Close() error {
|
||||
if m.CloseFunc == nil {
|
||||
return nil
|
||||
}
|
||||
return m.CloseFunc()
|
||||
}
|
||||
|
||||
func (m *MockClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error {
|
||||
if m.SyncFunc == nil {
|
||||
return nil
|
||||
}
|
||||
return m.SyncFunc(msgHandler)
|
||||
}
|
||||
|
||||
func (m *MockClient) GetServerPublicKey() (*wgtypes.Key, error) {
|
||||
if m.GetServerPublicKeyFunc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return m.GetServerPublicKeyFunc()
|
||||
}
|
||||
|
||||
func (m *MockClient) Register(serverKey wgtypes.Key, setupKey string, info *system.Info) (*proto.LoginResponse, error) {
|
||||
if m.RegisterFunc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return m.RegisterFunc(serverKey, setupKey, info)
|
||||
}
|
||||
|
||||
func (m *MockClient) Login(serverKey wgtypes.Key) (*proto.LoginResponse, error) {
|
||||
if m.LoginFunc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return m.LoginFunc(serverKey)
|
||||
}
|
||||
@@ -2,10 +2,12 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/http"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/idp"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"net"
|
||||
"os"
|
||||
@@ -25,6 +27,8 @@ var (
|
||||
mgmtDataDir string
|
||||
mgmtConfig string
|
||||
mgmtLetsencryptDomain string
|
||||
certFile string
|
||||
certKey string
|
||||
|
||||
kaep = keepalive.EnforcementPolicy{
|
||||
MinTime: 15 * time.Second,
|
||||
@@ -65,18 +69,38 @@ var (
|
||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
||||
}
|
||||
peersUpdateManager := server.NewPeersUpdateManager()
|
||||
accountManager := server.NewManager(store, peersUpdateManager)
|
||||
|
||||
var idpManager idp.Manager
|
||||
if config.IdpManagerConfig != nil {
|
||||
idpManager, err = idp.NewManager(*config.IdpManagerConfig)
|
||||
if err != nil {
|
||||
log.Fatalln("failed retrieving a new idp manager with err: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
accountManager := server.NewManager(store, peersUpdateManager, idpManager)
|
||||
|
||||
var opts []grpc.ServerOption
|
||||
|
||||
var httpServer *http.Server
|
||||
if config.HttpConfig.LetsEncryptDomain != "" {
|
||||
//automatically generate a new certificate with Let's Encrypt
|
||||
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 {
|
||||
log.Fatal("cannot load TLS credentials: ", err)
|
||||
}
|
||||
transportCredentials := credentials.NewTLS(tlsConfig)
|
||||
opts = append(opts, grpc.Creds(transportCredentials))
|
||||
httpServer = http.NewHttpsServerWithTLSConfig(config.HttpConfig, tlsConfig, accountManager)
|
||||
} else {
|
||||
//start server without SSL
|
||||
httpServer = http.NewHttpServer(config.HttpConfig, accountManager)
|
||||
}
|
||||
|
||||
@@ -136,14 +160,37 @@ func loadConfig() (*server.Config, error) {
|
||||
config.Datadir = mgmtDataDir
|
||||
}
|
||||
|
||||
if certKey != "" && certFile != "" {
|
||||
config.HttpConfig.CertFile = certFile
|
||||
config.HttpConfig.CertKey = certKey
|
||||
}
|
||||
|
||||
return config, err
|
||||
}
|
||||
|
||||
func loadTLSConfig(certFile string, certKey string) (*tls.Config, error) {
|
||||
// Load server's certificate and private key
|
||||
serverCert, err := tls.LoadX509KeyPair(certFile, certKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the credentials and return it
|
||||
config := &tls.Config{
|
||||
Certificates: []tls.Certificate{serverCert},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
mgmtCmd.Flags().IntVar(&mgmtPort, "port", 33073, "server port to listen on")
|
||||
mgmtCmd.Flags().StringVar(&mgmtDataDir, "datadir", "/var/lib/wiretrustee/", "server data directory location")
|
||||
mgmtCmd.Flags().StringVar(&mgmtConfig, "config", "/etc/wiretrustee/management.json", "Wiretrustee 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(&certFile, "cert-file", "", "Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
|
||||
mgmtCmd.Flags().StringVar(&certKey, "cert-key", "", "Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
|
||||
|
||||
rootCmd.MarkFlagRequired("config") //nolint
|
||||
|
||||
|
||||
@@ -85,6 +85,8 @@ type EncryptedMessage struct {
|
||||
WgPubKey string `protobuf:"bytes,1,opt,name=wgPubKey,proto3" json:"wgPubKey,omitempty"`
|
||||
// encrypted message Body
|
||||
Body []byte `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"`
|
||||
// Version of the Wiretrustee Management Service protocol
|
||||
Version int32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"`
|
||||
}
|
||||
|
||||
func (x *EncryptedMessage) Reset() {
|
||||
@@ -133,6 +135,13 @@ func (x *EncryptedMessage) GetBody() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *EncryptedMessage) GetVersion() int32 {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type SyncRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -178,11 +187,15 @@ type SyncResponse struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Global config
|
||||
WiretrusteeConfig *WiretrusteeConfig `protobuf:"bytes,1,opt,name=wiretrusteeConfig,proto3" json:"wiretrusteeConfig,omitempty"`
|
||||
PeerConfig *PeerConfig `protobuf:"bytes,2,opt,name=peerConfig,proto3" json:"peerConfig,omitempty"`
|
||||
RemotePeers []*RemotePeerConfig `protobuf:"bytes,3,rep,name=remotePeers,proto3" json:"remotePeers,omitempty"`
|
||||
WiretrusteeConfig *WiretrusteeConfig `protobuf:"bytes,1,opt,name=wiretrusteeConfig,proto3" json:"wiretrusteeConfig,omitempty"`
|
||||
// Deprecated. Use NetworkMap.PeerConfig
|
||||
PeerConfig *PeerConfig `protobuf:"bytes,2,opt,name=peerConfig,proto3" json:"peerConfig,omitempty"`
|
||||
// Deprecated. Use NetworkMap.RemotePeerConfig
|
||||
RemotePeers []*RemotePeerConfig `protobuf:"bytes,3,rep,name=remotePeers,proto3" json:"remotePeers,omitempty"`
|
||||
// Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
|
||||
RemotePeersIsEmpty bool `protobuf:"varint,4,opt,name=remotePeersIsEmpty,proto3" json:"remotePeersIsEmpty,omitempty"`
|
||||
// Deprecated. Use NetworkMap.remotePeersIsEmpty
|
||||
RemotePeersIsEmpty bool `protobuf:"varint,4,opt,name=remotePeersIsEmpty,proto3" json:"remotePeersIsEmpty,omitempty"`
|
||||
NetworkMap *NetworkMap `protobuf:"bytes,5,opt,name=NetworkMap,proto3" json:"NetworkMap,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SyncResponse) Reset() {
|
||||
@@ -245,6 +258,13 @@ func (x *SyncResponse) GetRemotePeersIsEmpty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *SyncResponse) GetNetworkMap() *NetworkMap {
|
||||
if x != nil {
|
||||
return x.NetworkMap
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -464,6 +484,8 @@ type ServerKeyResponse struct {
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
// Key expiration timestamp after which the key should be fetched again by the client
|
||||
ExpiresAt *timestamp.Timestamp `protobuf:"bytes,2,opt,name=expiresAt,proto3" json:"expiresAt,omitempty"`
|
||||
// Version of the Wiretrustee Management Service protocol
|
||||
Version int32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ServerKeyResponse) Reset() {
|
||||
@@ -512,6 +534,13 @@ func (x *ServerKeyResponse) GetExpiresAt() *timestamp.Timestamp {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ServerKeyResponse) GetVersion() int32 {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Empty struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -798,6 +827,84 @@ func (x *PeerConfig) GetDns() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections
|
||||
type NetworkMap struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Serial is an ID of the network state to be used by clients to order updates.
|
||||
// The larger the Serial the newer the configuration.
|
||||
// E.g. the client app should keep track of this id locally and discard all the configurations with a lower value
|
||||
Serial uint64 `protobuf:"varint,1,opt,name=Serial,proto3" json:"Serial,omitempty"`
|
||||
// PeerConfig represents configuration of a peer
|
||||
PeerConfig *PeerConfig `protobuf:"bytes,2,opt,name=peerConfig,proto3" json:"peerConfig,omitempty"`
|
||||
// RemotePeerConfig represents a list of remote peers that the receiver can connect to
|
||||
RemotePeers []*RemotePeerConfig `protobuf:"bytes,3,rep,name=remotePeers,proto3" json:"remotePeers,omitempty"`
|
||||
// Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
|
||||
RemotePeersIsEmpty bool `protobuf:"varint,4,opt,name=remotePeersIsEmpty,proto3" json:"remotePeersIsEmpty,omitempty"`
|
||||
}
|
||||
|
||||
func (x *NetworkMap) Reset() {
|
||||
*x = NetworkMap{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_management_proto_msgTypes[12]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *NetworkMap) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NetworkMap) ProtoMessage() {}
|
||||
|
||||
func (x *NetworkMap) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_management_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 NetworkMap.ProtoReflect.Descriptor instead.
|
||||
func (*NetworkMap) Descriptor() ([]byte, []int) {
|
||||
return file_management_proto_rawDescGZIP(), []int{12}
|
||||
}
|
||||
|
||||
func (x *NetworkMap) GetSerial() uint64 {
|
||||
if x != nil {
|
||||
return x.Serial
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NetworkMap) GetPeerConfig() *PeerConfig {
|
||||
if x != nil {
|
||||
return x.PeerConfig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NetworkMap) GetRemotePeers() []*RemotePeerConfig {
|
||||
if x != nil {
|
||||
return x.RemotePeers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NetworkMap) GetRemotePeersIsEmpty() bool {
|
||||
if x != nil {
|
||||
return x.RemotePeersIsEmpty
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemotePeerConfig represents a configuration of a remote peer.
|
||||
// The properties are used to configure Wireguard Peers sections
|
||||
type RemotePeerConfig struct {
|
||||
@@ -814,7 +921,7 @@ type RemotePeerConfig struct {
|
||||
func (x *RemotePeerConfig) Reset() {
|
||||
*x = RemotePeerConfig{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_management_proto_msgTypes[12]
|
||||
mi := &file_management_proto_msgTypes[13]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -827,7 +934,7 @@ func (x *RemotePeerConfig) String() string {
|
||||
func (*RemotePeerConfig) ProtoMessage() {}
|
||||
|
||||
func (x *RemotePeerConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_management_proto_msgTypes[12]
|
||||
mi := &file_management_proto_msgTypes[13]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -840,7 +947,7 @@ func (x *RemotePeerConfig) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RemotePeerConfig.ProtoReflect.Descriptor instead.
|
||||
func (*RemotePeerConfig) Descriptor() ([]byte, []int) {
|
||||
return file_management_proto_rawDescGZIP(), []int{12}
|
||||
return file_management_proto_rawDescGZIP(), []int{13}
|
||||
}
|
||||
|
||||
func (x *RemotePeerConfig) GetWgPubKey() string {
|
||||
@@ -864,12 +971,52 @@ var file_management_proto_rawDesc = []byte{
|
||||
0x74, 0x6f, 0x12, 0x0a, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x1f,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
|
||||
0x42, 0x0a, 0x10, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x5c, 0x0a, 0x10, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62,
|
||||
0x6f, 0x64, 0x79, 0x22, 0x0d, 0x0a, 0x0b, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x22, 0x83, 0x02, 0x0a, 0x0c, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6f, 0x64, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d, 0x0a,
|
||||
0x0b, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbb, 0x02, 0x0a,
|
||||
0x0c, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a,
|
||||
0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65,
|
||||
0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75,
|
||||
0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65,
|
||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
|
||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72,
|
||||
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65,
|
||||
0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72,
|
||||
0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12,
|
||||
0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x12, 0x36, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70,
|
||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x52, 0x0a,
|
||||
0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x22, 0x5a, 0x0a, 0x0c, 0x4c, 0x6f,
|
||||
0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65,
|
||||
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65,
|
||||
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61,
|
||||
0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0xc8, 0x01, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x53,
|
||||
0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73,
|
||||
0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73,
|
||||
0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x6f, 0x4f, 0x53, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x6f, 0x4f, 0x53, 0x12, 0x16, 0x0a, 0x06, 0x6b, 0x65, 0x72,
|
||||
0x6e, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6b, 0x65, 0x72, 0x6e, 0x65,
|
||||
0x6c, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x04, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72,
|
||||
0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72,
|
||||
0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x4f, 0x53, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x4f,
|
||||
0x53, 0x12, 0x2e, 0x0a, 0x12, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65,
|
||||
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x77,
|
||||
0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x22, 0x94, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74,
|
||||
0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d,
|
||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x72, 0x65,
|
||||
@@ -878,104 +1025,84 @@ var file_management_proto_rawDesc = []byte{
|
||||
0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65,
|
||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f,
|
||||
0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74,
|
||||
0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x72, 0x65, 0x6d,
|
||||
0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f,
|
||||
0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72,
|
||||
0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x5a, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x69,
|
||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x75,
|
||||
0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x74, 0x75,
|
||||
0x70, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04,
|
||||
0x6d, 0x65, 0x74, 0x61, 0x22, 0xc8, 0x01, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x73,
|
||||
0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x6f, 0x4f, 0x53, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x67, 0x6f, 0x4f, 0x53, 0x12, 0x16, 0x0a, 0x06, 0x6b, 0x65, 0x72, 0x6e, 0x65,
|
||||
0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63,
|
||||
0x6f, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18,
|
||||
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x4f, 0x53, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x4f, 0x53, 0x12,
|
||||
0x2e, 0x0a, 0x12, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x56, 0x65,
|
||||
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x77, 0x69, 0x72,
|
||||
0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22,
|
||||
0x94, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x4b, 0x0a, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72,
|
||||
0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x77, 0x69, 0x72,
|
||||
0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36,
|
||||
0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x5f, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
||||
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x38, 0x0a,
|
||||
0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78,
|
||||
0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x22, 0xa8, 0x01, 0x0a, 0x11, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x0a, 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73, 0x18,
|
||||
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||
0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x73,
|
||||
0x74, 0x75, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x18, 0x02, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x73,
|
||||
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61,
|
||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x22, 0x98, 0x01, 0x0a, 0x0a,
|
||||
0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72,
|
||||
0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x3b, 0x0a, 0x08,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f,
|
||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52,
|
||||
0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x3b, 0x0a, 0x08, 0x50, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x00, 0x12, 0x07,
|
||||
0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10,
|
||||
0x02, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04,
|
||||
0x44, 0x54, 0x4c, 0x53, 0x10, 0x04, 0x22, 0x7d, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63,
|
||||
0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a,
|
||||
0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x79, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76,
|
||||
0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a,
|
||||
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
|
||||
0x38, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09,
|
||||
0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xa8, 0x01, 0x0a,
|
||||
0x11, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x12, 0x2c, 0x0a, 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48,
|
||||
0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73,
|
||||
0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73,
|
||||
0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x38, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a,
|
||||
0x03, 0x64, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x6e, 0x73, 0x22,
|
||||
0x4e, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12,
|
||||
0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x18, 0x02, 0x20,
|
||||
0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x32,
|
||||
0x9b, 0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c,
|
||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72,
|
||||
0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
|
||||
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04,
|
||||
0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
||||
0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65,
|
||||
0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65,
|
||||
0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||
0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x42, 0x08, 0x5a,
|
||||
0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73,
|
||||
0x12, 0x35, 0x0a, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f,
|
||||
0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||
0x52, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61,
|
||||
0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
|
||||
0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x22, 0x98, 0x01, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x3b, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e,
|
||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x3b, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
|
||||
0x6c, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43,
|
||||
0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x02, 0x12, 0x09, 0x0a,
|
||||
0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x54, 0x4c, 0x53,
|
||||
0x10, 0x04, 0x22, 0x7d, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48,
|
||||
0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x68, 0x6f, 0x73,
|
||||
0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||
0x67, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
|
||||
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
|
||||
0x64, 0x22, 0x38, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x6e, 0x73,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x6e, 0x73, 0x22, 0xcc, 0x01, 0x0a, 0x0a,
|
||||
0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x65,
|
||||
0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, 0x69,
|
||||
0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a,
|
||||
0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0b, 0x72, 0x65,
|
||||
0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d,
|
||||
0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x72,
|
||||
0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65,
|
||||
0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65,
|
||||
0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x4e, 0x0a, 0x10, 0x52, 0x65,
|
||||
0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a,
|
||||
0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c,
|
||||
0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a,
|
||||
0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x32, 0x9b, 0x02, 0x0a, 0x11, 0x4d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
|
||||
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65,
|
||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12,
|
||||
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63,
|
||||
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
|
||||
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12,
|
||||
0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12,
|
||||
0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79,
|
||||
0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d,
|
||||
0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -991,7 +1118,7 @@ func file_management_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
|
||||
var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 14)
|
||||
var file_management_proto_goTypes = []interface{}{
|
||||
(HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol
|
||||
(*EncryptedMessage)(nil), // 1: management.EncryptedMessage
|
||||
@@ -1006,35 +1133,39 @@ var file_management_proto_goTypes = []interface{}{
|
||||
(*HostConfig)(nil), // 10: management.HostConfig
|
||||
(*ProtectedHostConfig)(nil), // 11: management.ProtectedHostConfig
|
||||
(*PeerConfig)(nil), // 12: management.PeerConfig
|
||||
(*RemotePeerConfig)(nil), // 13: management.RemotePeerConfig
|
||||
(*timestamp.Timestamp)(nil), // 14: google.protobuf.Timestamp
|
||||
(*NetworkMap)(nil), // 13: management.NetworkMap
|
||||
(*RemotePeerConfig)(nil), // 14: management.RemotePeerConfig
|
||||
(*timestamp.Timestamp)(nil), // 15: google.protobuf.Timestamp
|
||||
}
|
||||
var file_management_proto_depIdxs = []int32{
|
||||
9, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
||||
12, // 1: management.SyncResponse.peerConfig:type_name -> management.PeerConfig
|
||||
13, // 2: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig
|
||||
5, // 3: management.LoginRequest.meta:type_name -> management.PeerSystemMeta
|
||||
9, // 4: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
||||
12, // 5: management.LoginResponse.peerConfig:type_name -> management.PeerConfig
|
||||
14, // 6: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp
|
||||
10, // 7: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig
|
||||
11, // 8: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig
|
||||
10, // 9: management.WiretrusteeConfig.signal:type_name -> management.HostConfig
|
||||
0, // 10: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol
|
||||
10, // 11: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig
|
||||
1, // 12: management.ManagementService.Login:input_type -> management.EncryptedMessage
|
||||
1, // 13: management.ManagementService.Sync:input_type -> management.EncryptedMessage
|
||||
8, // 14: management.ManagementService.GetServerKey:input_type -> management.Empty
|
||||
8, // 15: management.ManagementService.isHealthy:input_type -> management.Empty
|
||||
1, // 16: management.ManagementService.Login:output_type -> management.EncryptedMessage
|
||||
1, // 17: management.ManagementService.Sync:output_type -> management.EncryptedMessage
|
||||
7, // 18: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
|
||||
8, // 19: management.ManagementService.isHealthy:output_type -> management.Empty
|
||||
16, // [16:20] is the sub-list for method output_type
|
||||
12, // [12:16] is the sub-list for method input_type
|
||||
12, // [12:12] is the sub-list for extension type_name
|
||||
12, // [12:12] is the sub-list for extension extendee
|
||||
0, // [0:12] is the sub-list for field type_name
|
||||
14, // 2: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig
|
||||
13, // 3: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap
|
||||
5, // 4: management.LoginRequest.meta:type_name -> management.PeerSystemMeta
|
||||
9, // 5: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
||||
12, // 6: management.LoginResponse.peerConfig:type_name -> management.PeerConfig
|
||||
15, // 7: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp
|
||||
10, // 8: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig
|
||||
11, // 9: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig
|
||||
10, // 10: management.WiretrusteeConfig.signal:type_name -> management.HostConfig
|
||||
0, // 11: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol
|
||||
10, // 12: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig
|
||||
12, // 13: management.NetworkMap.peerConfig:type_name -> management.PeerConfig
|
||||
14, // 14: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig
|
||||
1, // 15: management.ManagementService.Login:input_type -> management.EncryptedMessage
|
||||
1, // 16: management.ManagementService.Sync:input_type -> management.EncryptedMessage
|
||||
8, // 17: management.ManagementService.GetServerKey:input_type -> management.Empty
|
||||
8, // 18: management.ManagementService.isHealthy:input_type -> management.Empty
|
||||
1, // 19: management.ManagementService.Login:output_type -> management.EncryptedMessage
|
||||
1, // 20: management.ManagementService.Sync:output_type -> management.EncryptedMessage
|
||||
7, // 21: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
|
||||
8, // 22: management.ManagementService.isHealthy:output_type -> management.Empty
|
||||
19, // [19:23] is the sub-list for method output_type
|
||||
15, // [15:19] is the sub-list for method input_type
|
||||
15, // [15:15] is the sub-list for extension type_name
|
||||
15, // [15:15] is the sub-list for extension extendee
|
||||
0, // [0:15] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_management_proto_init() }
|
||||
@@ -1188,6 +1319,18 @@ func file_management_proto_init() {
|
||||
}
|
||||
}
|
||||
file_management_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*NetworkMap); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_management_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RemotePeerConfig); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -1206,7 +1349,7 @@ func file_management_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_management_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 13,
|
||||
NumMessages: 14,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -9,11 +9,13 @@ package management;
|
||||
service ManagementService {
|
||||
|
||||
// Login logs in peer. In case server returns codes.PermissionDenied this endpoint can be used to register Peer providing LoginRequest.setupKey
|
||||
// Returns encrypted LoginResponse in EncryptedMessage.Body
|
||||
rpc Login(EncryptedMessage) returns (EncryptedMessage) {}
|
||||
|
||||
// Sync enables peer synchronization. Each peer that is connected to this stream will receive updates from the server.
|
||||
// For example, if a new peer has been added to an account all other connected peers will receive this peer's Wireguard public key as an update
|
||||
// The initial SyncResponse contains all of the available peers so the local state can be refreshed
|
||||
// Returns encrypted SyncResponse in EncryptedMessage.Body
|
||||
rpc Sync(EncryptedMessage) returns (stream EncryptedMessage) {}
|
||||
|
||||
// Exposes a Wireguard public key of the Management service.
|
||||
@@ -30,20 +32,29 @@ message EncryptedMessage {
|
||||
|
||||
// encrypted message Body
|
||||
bytes body = 2;
|
||||
// Version of the Wiretrustee Management Service protocol
|
||||
int32 version = 3;
|
||||
}
|
||||
|
||||
message SyncRequest {}
|
||||
|
||||
// SyncResponse represents a state that should be applied to the local peer (e.g. Wiretrustee servers config as well as local peer and remote peers configs)
|
||||
message SyncResponse {
|
||||
|
||||
// Global config
|
||||
WiretrusteeConfig wiretrusteeConfig = 1;
|
||||
|
||||
// Deprecated. Use NetworkMap.PeerConfig
|
||||
PeerConfig peerConfig = 2;
|
||||
|
||||
// Deprecated. Use NetworkMap.RemotePeerConfig
|
||||
repeated RemotePeerConfig remotePeers = 3;
|
||||
|
||||
// Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
|
||||
// Deprecated. Use NetworkMap.remotePeersIsEmpty
|
||||
bool remotePeersIsEmpty = 4;
|
||||
|
||||
NetworkMap NetworkMap = 5;
|
||||
}
|
||||
|
||||
message LoginRequest {
|
||||
@@ -76,6 +87,8 @@ message ServerKeyResponse {
|
||||
string key = 1;
|
||||
// Key expiration timestamp after which the key should be fetched again by the client
|
||||
google.protobuf.Timestamp expiresAt = 2;
|
||||
// Version of the Wiretrustee Management Service protocol
|
||||
int32 version = 3;
|
||||
}
|
||||
|
||||
message Empty {}
|
||||
@@ -122,6 +135,24 @@ message PeerConfig {
|
||||
string dns = 2;
|
||||
}
|
||||
|
||||
// NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections
|
||||
message NetworkMap {
|
||||
// Serial is an ID of the network state to be used by clients to order updates.
|
||||
// The larger the Serial the newer the configuration.
|
||||
// E.g. the client app should keep track of this id locally and discard all the configurations with a lower value
|
||||
uint64 Serial = 1;
|
||||
|
||||
// PeerConfig represents configuration of a peer
|
||||
PeerConfig peerConfig = 2;
|
||||
|
||||
// RemotePeerConfig represents a list of remote peers that the receiver can connect to
|
||||
repeated RemotePeerConfig remotePeers = 3;
|
||||
|
||||
// Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
|
||||
bool remotePeersIsEmpty = 4;
|
||||
|
||||
}
|
||||
|
||||
// RemotePeerConfig represents a configuration of a remote peer.
|
||||
// The properties are used to configure Wireguard Peers sections
|
||||
message RemotePeerConfig {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/xid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/idp"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AccountManager struct {
|
||||
@@ -15,36 +15,79 @@ type AccountManager struct {
|
||||
// mutex to synchronise account operations (e.g. generating Peer IP address inside the Network)
|
||||
mux sync.Mutex
|
||||
peersUpdateManager *PeersUpdateManager
|
||||
idpManager idp.Manager
|
||||
}
|
||||
|
||||
// Account represents a unique account of the system
|
||||
type Account struct {
|
||||
Id string
|
||||
Id string
|
||||
// User.Id it was created by
|
||||
CreatedBy string
|
||||
Domain string
|
||||
SetupKeys map[string]*SetupKey
|
||||
Network *Network
|
||||
Peers map[string]*Peer
|
||||
Users map[string]*User
|
||||
}
|
||||
|
||||
// NewAccount creates a new Account with a generated ID and generated default setup keys
|
||||
func NewAccount(userId, domain string) *Account {
|
||||
accountId := xid.New().String()
|
||||
return newAccountWithId(accountId, userId, domain)
|
||||
}
|
||||
|
||||
func (a *Account) Copy() *Account {
|
||||
peers := map[string]*Peer{}
|
||||
for id, peer := range a.Peers {
|
||||
peers[id] = peer.Copy()
|
||||
}
|
||||
|
||||
users := map[string]*User{}
|
||||
for id, user := range a.Users {
|
||||
users[id] = user.Copy()
|
||||
}
|
||||
|
||||
setupKeys := map[string]*SetupKey{}
|
||||
for id, key := range a.SetupKeys {
|
||||
setupKeys[id] = key.Copy()
|
||||
}
|
||||
|
||||
return &Account{
|
||||
Id: a.Id,
|
||||
CreatedBy: a.CreatedBy,
|
||||
SetupKeys: setupKeys,
|
||||
Network: a.Network.Copy(),
|
||||
Peers: peers,
|
||||
Users: users,
|
||||
}
|
||||
}
|
||||
|
||||
// NewManager creates a new AccountManager with a provided Store
|
||||
func NewManager(store Store, peersUpdateManager *PeersUpdateManager) *AccountManager {
|
||||
func NewManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager) *AccountManager {
|
||||
return &AccountManager{
|
||||
Store: store,
|
||||
mux: sync.Mutex{},
|
||||
peersUpdateManager: peersUpdateManager,
|
||||
idpManager: idpManager,
|
||||
}
|
||||
}
|
||||
|
||||
//AddSetupKey generates a new setup key with a given name and type, and adds it to the specified account
|
||||
func (am *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn time.Duration) (*SetupKey, error) {
|
||||
func (am *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn *util.Duration) (*SetupKey, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
keyDuration := DefaultSetupKeyDuration
|
||||
if expiresIn != nil {
|
||||
keyDuration = expiresIn.Duration
|
||||
}
|
||||
|
||||
account, err := am.Store.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||
}
|
||||
|
||||
setupKey := GenerateSetupKey(keyName, keyType, expiresIn)
|
||||
setupKey := GenerateSetupKey(keyName, keyType, keyDuration)
|
||||
account.SetupKeys[setupKey.Key] = setupKey
|
||||
|
||||
err = am.Store.SaveAccount(account)
|
||||
@@ -107,8 +150,8 @@ func (am *AccountManager) RenameSetupKey(accountId string, keyId string, newName
|
||||
return keyCopy, nil
|
||||
}
|
||||
|
||||
//GetAccount returns an existing account or error (NotFound) if doesn't exist
|
||||
func (am *AccountManager) GetAccount(accountId string) (*Account, error) {
|
||||
//GetAccountById returns an existing account using its ID or error (NotFound) if doesn't exist
|
||||
func (am *AccountManager) GetAccountById(accountId string) (*Account, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
@@ -120,27 +163,28 @@ func (am *AccountManager) GetAccount(accountId string) (*Account, error) {
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// GetOrCreateAccount returns an existing account or creates a new one if doesn't exist
|
||||
func (am *AccountManager) GetOrCreateAccount(accountId string) (*Account, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
//GetAccountByUserOrAccountId look for an account by user or account Id, if no account is provided and
|
||||
// user id doesn't have an account associated with it, one account is created
|
||||
func (am *AccountManager) GetAccountByUserOrAccountId(userId, accountId, domain string) (*Account, error) {
|
||||
|
||||
_, err := am.Store.GetAccount(accountId)
|
||||
if err != nil {
|
||||
if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
|
||||
return am.createAccount(accountId)
|
||||
} else {
|
||||
// other error
|
||||
return nil, err
|
||||
if accountId != "" {
|
||||
return am.GetAccountById(accountId)
|
||||
} else if userId != "" {
|
||||
account, err := am.GetOrCreateAccountByUser(userId, domain)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "account not found using user id: %s", userId)
|
||||
}
|
||||
// update idp manager app metadata
|
||||
if am.idpManager != nil {
|
||||
err = am.idpManager.UpdateUserAppMetadata(userId, idp.AppMetadata{WTAccountId: account.Id})
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "updating user's app metadata failed with: %v", err)
|
||||
}
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
account, err := am.Store.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed retrieving account")
|
||||
}
|
||||
|
||||
return account, nil
|
||||
return nil, status.Errorf(codes.NotFound, "no valid user or account Id provided")
|
||||
}
|
||||
|
||||
//AccountExists checks whether account exists (returns true) or not (returns false)
|
||||
@@ -163,18 +207,18 @@ func (am *AccountManager) AccountExists(accountId string) (*bool, error) {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// AddAccount generates a new Account with a provided accountId and saves to the Store
|
||||
func (am *AccountManager) AddAccount(accountId string) (*Account, error) {
|
||||
// AddAccount generates a new Account with a provided accountId and userId, saves to the Store
|
||||
func (am *AccountManager) AddAccount(accountId, userId, domain string) (*Account, error) {
|
||||
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
return am.createAccount(accountId)
|
||||
return am.createAccount(accountId, userId, domain)
|
||||
|
||||
}
|
||||
|
||||
func (am *AccountManager) createAccount(accountId string) (*Account, error) {
|
||||
account, _ := newAccountWithId(accountId)
|
||||
func (am *AccountManager) createAccount(accountId, userId, domain string) (*Account, error) {
|
||||
account := newAccountWithId(accountId, userId, domain)
|
||||
|
||||
err := am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
@@ -185,7 +229,7 @@ func (am *AccountManager) createAccount(accountId string) (*Account, error) {
|
||||
}
|
||||
|
||||
// newAccountWithId creates a new Account with a default SetupKey (doesn't store in a Store) and provided id
|
||||
func newAccountWithId(accountId string) (*Account, *SetupKey) {
|
||||
func newAccountWithId(accountId, userId, domain string) *Account {
|
||||
|
||||
log.Debugf("creating new account")
|
||||
|
||||
@@ -194,21 +238,21 @@ func newAccountWithId(accountId string) (*Account, *SetupKey) {
|
||||
oneOffKey := GenerateSetupKey("One-off key", SetupKeyOneOff, DefaultSetupKeyDuration)
|
||||
setupKeys[defaultKey.Key] = defaultKey
|
||||
setupKeys[oneOffKey.Key] = oneOffKey
|
||||
network := &Network{
|
||||
Id: uuid.New().String(),
|
||||
Net: net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 192, 0, 0}},
|
||||
Dns: ""}
|
||||
network := NewNetwork()
|
||||
peers := make(map[string]*Peer)
|
||||
users := make(map[string]*User)
|
||||
|
||||
log.Debugf("created new account %s with setup key %s", accountId, defaultKey.Key)
|
||||
|
||||
return &Account{Id: accountId, SetupKeys: setupKeys, Network: network, Peers: peers}, defaultKey
|
||||
}
|
||||
|
||||
// newAccount creates a new Account with a default SetupKey (doesn't store in a Store)
|
||||
func newAccount() (*Account, *SetupKey) {
|
||||
accountId := uuid.New().String()
|
||||
return newAccountWithId(accountId)
|
||||
return &Account{
|
||||
Id: accountId,
|
||||
SetupKeys: setupKeys,
|
||||
Network: network,
|
||||
Peers: peers,
|
||||
Users: users,
|
||||
CreatedBy: userId,
|
||||
Domain: domain,
|
||||
}
|
||||
}
|
||||
|
||||
func getAccountSetupKeyById(acc *Account, keyId string) *SetupKey {
|
||||
|
||||
@@ -2,12 +2,73 @@ package server
|
||||
|
||||
import (
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAccountManager_GetOrCreateAccountByUser(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
userId := "test_user"
|
||||
account, err := manager.GetOrCreateAccountByUser(userId, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if account == nil {
|
||||
t.Fatalf("expected to create an account for a user %s", userId)
|
||||
}
|
||||
|
||||
account, err = manager.GetAccountByUser(userId)
|
||||
if err != nil {
|
||||
t.Errorf("expected to get existing account after creation, no account was found for a user %s", userId)
|
||||
}
|
||||
|
||||
if account != nil && account.Users[userId] == nil {
|
||||
t.Fatalf("expected to create an account for a user %s but no user was found after creation udner the account %s", userId, account.Id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountManager_SetOrUpdateDomain(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
userId := "test_user"
|
||||
domain := "hotmail.com"
|
||||
account, err := manager.GetOrCreateAccountByUser(userId, domain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if account == nil {
|
||||
t.Fatalf("expected to create an account for a user %s", userId)
|
||||
}
|
||||
|
||||
if account.Domain != domain {
|
||||
t.Errorf("setting account domain failed, expected %s, got %s", domain, account.Domain)
|
||||
}
|
||||
|
||||
domain = "gmail.com"
|
||||
|
||||
account, err = manager.GetOrCreateAccountByUser(userId, domain)
|
||||
if err != nil {
|
||||
t.Fatalf("got the following error while retrieving existing acc: %v", err)
|
||||
}
|
||||
|
||||
if account == nil {
|
||||
t.Fatalf("expected to get an account for a user %s", userId)
|
||||
}
|
||||
|
||||
if account.Domain != domain {
|
||||
t.Errorf("updating domain. expected %s got %s", domain, account.Domain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountManager_AddAccount(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
@@ -16,6 +77,7 @@ func TestAccountManager_AddAccount(t *testing.T) {
|
||||
}
|
||||
|
||||
expectedId := "test_account"
|
||||
userId := "account_creator"
|
||||
expectedPeersSize := 0
|
||||
expectedSetupKeysSize := 2
|
||||
expectedNetwork := net.IPNet{
|
||||
@@ -23,7 +85,7 @@ func TestAccountManager_AddAccount(t *testing.T) {
|
||||
Mask: net.IPMask{255, 192, 0, 0},
|
||||
}
|
||||
|
||||
account, err := manager.AddAccount(expectedId)
|
||||
account, err := manager.AddAccount(expectedId, userId, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -45,43 +107,33 @@ func TestAccountManager_AddAccount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountManager_GetOrCreateAccount(t *testing.T) {
|
||||
func TestAccountManager_GetAccountByUserOrAccountId(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
expectedId := "test_account"
|
||||
userId := "test_user"
|
||||
|
||||
//make sure account doesn't exist
|
||||
account, err := manager.GetAccount(expectedId)
|
||||
if err != nil {
|
||||
errStatus, ok := status.FromError(err)
|
||||
if !(ok && errStatus.Code() == codes.NotFound) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if account != nil {
|
||||
t.Fatal("expecting empty account")
|
||||
}
|
||||
|
||||
account, err = manager.GetOrCreateAccount(expectedId)
|
||||
account, err := manager.GetAccountByUserOrAccountId(userId, "", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if account.Id != expectedId {
|
||||
t.Fatalf("expected to create an account, got wrong account")
|
||||
if account == nil {
|
||||
t.Fatalf("expected to create an account for a user %s", userId)
|
||||
}
|
||||
|
||||
account, err = manager.GetOrCreateAccount(expectedId)
|
||||
accountId := account.Id
|
||||
|
||||
_, err = manager.GetAccountByUserOrAccountId("", accountId, "")
|
||||
if err != nil {
|
||||
t.Errorf("expected to get existing account after creation, failed")
|
||||
t.Errorf("expected to get existing account after creation using userid, no account was found for a account %s", accountId)
|
||||
}
|
||||
|
||||
if account.Id != expectedId {
|
||||
t.Fatalf("expected to create an account, got wrong account")
|
||||
_, err = manager.GetAccountByUserOrAccountId("", "", "")
|
||||
if err == nil {
|
||||
t.Errorf("expected an error when user and account IDs are empty")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +145,8 @@ func TestAccountManager_AccountExists(t *testing.T) {
|
||||
}
|
||||
|
||||
expectedId := "test_account"
|
||||
_, err = manager.AddAccount(expectedId)
|
||||
userId := "account_creator"
|
||||
_, err = manager.AddAccount(expectedId, userId, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -117,13 +170,14 @@ func TestAccountManager_GetAccount(t *testing.T) {
|
||||
}
|
||||
|
||||
expectedId := "test_account"
|
||||
account, err := manager.AddAccount(expectedId)
|
||||
userId := "account_creator"
|
||||
account, err := manager.AddAccount(expectedId, userId, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//AddAccount has been already tested so we can assume it is correct and compare results
|
||||
getAccount, err := manager.GetAccount(expectedId)
|
||||
getAccount, err := manager.GetAccountById(expectedId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
@@ -154,11 +208,13 @@ func TestAccountManager_AddPeer(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
account, err := manager.AddAccount("test_account")
|
||||
account, err := manager.AddAccount("test_account", "account_creator", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
serial := account.Network.Serial() //should be 0
|
||||
|
||||
var setupKey *SetupKey
|
||||
for _, key := range account.SetupKeys {
|
||||
setupKey = key
|
||||
@@ -169,7 +225,12 @@ func TestAccountManager_AddPeer(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
key, err := wgtypes.GenerateKey()
|
||||
if account.Network.serial != 0 {
|
||||
t.Errorf("expecting account network to have an initial serial=0")
|
||||
return
|
||||
}
|
||||
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
@@ -177,7 +238,7 @@ func TestAccountManager_AddPeer(t *testing.T) {
|
||||
expectedPeerKey := key.PublicKey().String()
|
||||
expectedPeerIP := "100.64.0.1"
|
||||
|
||||
peer, err := manager.AddPeer(setupKey.Key, Peer{
|
||||
peer, err := manager.AddPeer(setupKey.Key, &Peer{
|
||||
Key: expectedPeerKey,
|
||||
Meta: PeerSystemMeta{},
|
||||
Name: expectedPeerKey,
|
||||
@@ -187,6 +248,12 @@ func TestAccountManager_AddPeer(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
account, err = manager.GetAccountById(account.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if peer.Key != expectedPeerKey {
|
||||
t.Errorf("expecting just added peer to have key = %s, got %s", expectedPeerKey, peer.Key)
|
||||
}
|
||||
@@ -195,13 +262,70 @@ func TestAccountManager_AddPeer(t *testing.T) {
|
||||
t.Errorf("expecting just added peer to have IP = %s, got %s", expectedPeerIP, peer.IP.String())
|
||||
}
|
||||
|
||||
if account.Network.Serial() != 1 {
|
||||
t.Errorf("expecting Network serial=%d to be incremented by 1 and be equal to %d when adding new peer to account", serial, account.Network.Serial())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAccountManager_DeletePeer(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
account, err := manager.AddAccount("test_account", "account_creator", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var setupKey *SetupKey
|
||||
for _, key := range account.SetupKeys {
|
||||
setupKey = key
|
||||
}
|
||||
|
||||
key, err := wgtypes.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
peerKey := key.PublicKey().String()
|
||||
|
||||
_, err = manager.AddPeer(setupKey.Key, &Peer{
|
||||
Key: peerKey,
|
||||
Meta: PeerSystemMeta{},
|
||||
Name: peerKey,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("expecting peer to be added, got failure %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = manager.DeletePeer(account.Id, peerKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
account, err = manager.GetAccountById(account.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if account.Network.Serial() != 2 {
|
||||
t.Errorf("expecting Network serial=%d to be incremented and be equal to 2 after adding and deleteing a peer", account.Network.Serial())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func createManager(t *testing.T) (*AccountManager, error) {
|
||||
store, err := createStore(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewManager(store, NewPeersUpdateManager()), nil
|
||||
return NewManager(store, NewPeersUpdateManager(), nil), nil
|
||||
}
|
||||
|
||||
func createStore(t *testing.T) (Store, error) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/wiretrustee/wiretrustee/management/server/idp"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
)
|
||||
|
||||
@@ -23,6 +24,8 @@ type Config struct {
|
||||
Datadir string
|
||||
|
||||
HttpConfig *HttpServerConfig
|
||||
|
||||
IdpManagerConfig *idp.Config
|
||||
}
|
||||
|
||||
// TURNConfig is a config of the TURNCredentialsManager
|
||||
@@ -36,7 +39,11 @@ type TURNConfig struct {
|
||||
// HttpServerConfig is a config of the HTTP Management service server
|
||||
type HttpServerConfig struct {
|
||||
LetsEncryptDomain string
|
||||
Address string
|
||||
//CertFile is the location of the certificate
|
||||
CertFile string
|
||||
//CertKey is the location of the certificate private key
|
||||
CertKey string
|
||||
Address string
|
||||
// AuthAudience identifies the recipients that the JWT is intended for (aud in JWT)
|
||||
AuthAudience string
|
||||
// AuthIssuer identifies principal that issued the JWT.
|
||||
|
||||
@@ -20,6 +20,7 @@ type FileStore struct {
|
||||
Accounts map[string]*Account
|
||||
SetupKeyId2AccountId map[string]string `json:"-"`
|
||||
PeerKeyId2AccountId map[string]string `json:"-"`
|
||||
UserId2AccountId map[string]string `json:"-"`
|
||||
|
||||
// mutex to synchronise Store read/write operations
|
||||
mux sync.Mutex `json:"-"`
|
||||
@@ -45,6 +46,7 @@ func restore(file string) (*FileStore, error) {
|
||||
mux: sync.Mutex{},
|
||||
SetupKeyId2AccountId: make(map[string]string),
|
||||
PeerKeyId2AccountId: make(map[string]string),
|
||||
UserId2AccountId: make(map[string]string),
|
||||
storeFile: file,
|
||||
}
|
||||
|
||||
@@ -65,6 +67,7 @@ func restore(file string) (*FileStore, error) {
|
||||
store.storeFile = file
|
||||
store.SetupKeyId2AccountId = make(map[string]string)
|
||||
store.PeerKeyId2AccountId = make(map[string]string)
|
||||
store.UserId2AccountId = make(map[string]string)
|
||||
for accountId, account := range store.Accounts {
|
||||
for setupKeyId := range account.SetupKeys {
|
||||
store.SetupKeyId2AccountId[strings.ToUpper(setupKeyId)] = accountId
|
||||
@@ -72,6 +75,9 @@ func restore(file string) (*FileStore, error) {
|
||||
for _, peer := range account.Peers {
|
||||
store.PeerKeyId2AccountId[peer.Key] = accountId
|
||||
}
|
||||
for _, user := range account.Users {
|
||||
store.UserId2AccountId[user.Id] = accountId
|
||||
}
|
||||
}
|
||||
|
||||
return store, nil
|
||||
@@ -168,6 +174,10 @@ func (s *FileStore) SaveAccount(account *Account) error {
|
||||
s.PeerKeyId2AccountId[peer.Key] = account.Id
|
||||
}
|
||||
|
||||
for _, user := range account.Users {
|
||||
s.UserId2AccountId[user.Id] = account.Id
|
||||
}
|
||||
|
||||
err := s.persist(s.storeFile)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -217,6 +227,18 @@ func (s *FileStore) GetAccount(accountId string) (*Account, error) {
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (s *FileStore) GetUserAccount(userId string) (*Account, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
accountId, accountIdFound := s.UserId2AccountId[userId]
|
||||
if !accountIdFound {
|
||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||
}
|
||||
|
||||
return s.GetAccount(accountId)
|
||||
}
|
||||
|
||||
func (s *FileStore) GetPeerAccount(peerKey string) (*Account, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
171
management/server/file_store_test.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewStore(t *testing.T) {
|
||||
store := newStore(t)
|
||||
|
||||
if store.Accounts == nil || len(store.Accounts) != 0 {
|
||||
t.Errorf("expected to create a new empty Accounts map when creating a new FileStore")
|
||||
}
|
||||
|
||||
if store.SetupKeyId2AccountId == nil || len(store.SetupKeyId2AccountId) != 0 {
|
||||
t.Errorf("expected to create a new empty SetupKeyId2AccountId map when creating a new FileStore")
|
||||
}
|
||||
|
||||
if store.PeerKeyId2AccountId == nil || len(store.PeerKeyId2AccountId) != 0 {
|
||||
t.Errorf("expected to create a new empty PeerKeyId2AccountId map when creating a new FileStore")
|
||||
}
|
||||
|
||||
if store.UserId2AccountId == nil || len(store.UserId2AccountId) != 0 {
|
||||
t.Errorf("expected to create a new empty UserId2AccountId map when creating a new FileStore")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSaveAccount(t *testing.T) {
|
||||
store := newStore(t)
|
||||
|
||||
account := NewAccount("testuser", "")
|
||||
account.Users["testuser"] = NewAdminUser("testuser")
|
||||
setupKey := GenerateDefaultSetupKey()
|
||||
account.SetupKeys[setupKey.Key] = setupKey
|
||||
account.Peers["testpeer"] = &Peer{
|
||||
Key: "peerkey",
|
||||
SetupKey: "peerkeysetupkey",
|
||||
IP: net.IP{127, 0, 0, 1},
|
||||
Meta: PeerSystemMeta{},
|
||||
Name: "peer name",
|
||||
Status: &PeerStatus{Connected: true, LastSeen: time.Now()},
|
||||
}
|
||||
|
||||
// SaveAccount should trigger persist
|
||||
err := store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if store.Accounts[account.Id] == nil {
|
||||
t.Errorf("expecting Account to be stored after SaveAccount()")
|
||||
}
|
||||
|
||||
if store.PeerKeyId2AccountId["peerkey"] == "" {
|
||||
t.Errorf("expecting PeerKeyId2AccountId index updated after SaveAccount()")
|
||||
}
|
||||
|
||||
if store.UserId2AccountId["testuser"] == "" {
|
||||
t.Errorf("expecting UserId2AccountId index updated after SaveAccount()")
|
||||
}
|
||||
|
||||
if store.SetupKeyId2AccountId[setupKey.Key] == "" {
|
||||
t.Errorf("expecting SetupKeyId2AccountId index updated after SaveAccount()")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
store := newStore(t)
|
||||
|
||||
account := NewAccount("testuser", "")
|
||||
account.Users["testuser"] = NewAdminUser("testuser")
|
||||
account.Peers["testpeer"] = &Peer{
|
||||
Key: "peerkey",
|
||||
SetupKey: "peerkeysetupkey",
|
||||
IP: net.IP{127, 0, 0, 1},
|
||||
Meta: PeerSystemMeta{},
|
||||
Name: "peer name",
|
||||
Status: &PeerStatus{Connected: true, LastSeen: time.Now()},
|
||||
}
|
||||
|
||||
// SaveAccount should trigger persist
|
||||
err := store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
restored, err := NewStore(store.storeFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
restoredAccount := restored.Accounts[account.Id]
|
||||
if restoredAccount == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Account %s", account.Id)
|
||||
}
|
||||
|
||||
if restoredAccount != nil && restoredAccount.Peers["testpeer"] == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Peer testpeer")
|
||||
}
|
||||
|
||||
if restoredAccount != nil && restoredAccount.CreatedBy != "testuser" {
|
||||
t.Errorf("failed to restore a FileStore file - missing Account CreatedBy")
|
||||
}
|
||||
|
||||
if restoredAccount != nil && restoredAccount.Users["testuser"] == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing User testuser")
|
||||
}
|
||||
|
||||
if restoredAccount != nil && restoredAccount.Network == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Network")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRestore(t *testing.T) {
|
||||
storeDir := t.TempDir()
|
||||
|
||||
err := util.CopyFileContents("testdata/store.json", filepath.Join(storeDir, "store.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewStore(storeDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
account := store.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
|
||||
if account == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing account bf1c8084-ba50-4ce7-9439-34653001fc3b")
|
||||
}
|
||||
|
||||
if account != nil && account.Users["edafee4e-63fb-11ec-90d6-0242ac120003"] == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Account User edafee4e-63fb-11ec-90d6-0242ac120003")
|
||||
}
|
||||
|
||||
if account != nil && account.Users["f4f6d672-63fb-11ec-90d6-0242ac120003"] == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Account User f4f6d672-63fb-11ec-90d6-0242ac120003")
|
||||
}
|
||||
|
||||
if account != nil && account.Network == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Account Network")
|
||||
}
|
||||
|
||||
if account != nil && account.SetupKeys["A2C8E62B-38F5-4553-B31E-DD66C696CEBB"] == nil {
|
||||
t.Errorf("failed to restore a FileStore file - missing Account SetupKey A2C8E62B-38F5-4553-B31E-DD66C696CEBB")
|
||||
}
|
||||
|
||||
if len(store.UserId2AccountId) != 2 {
|
||||
t.Errorf("failed to restore a FileStore wrong UserId2AccountId mapping")
|
||||
}
|
||||
|
||||
if len(store.SetupKeyId2AccountId) != 1 {
|
||||
t.Errorf("failed to restore a FileStore wrong SetupKeyId2AccountId mapping")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func newStore(t *testing.T) *FileStore {
|
||||
store, err := NewStore(t.TempDir())
|
||||
if err != nil {
|
||||
t.Errorf("failed creating a new store")
|
||||
}
|
||||
|
||||
return store
|
||||
}
|
||||
@@ -141,7 +141,7 @@ func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Pe
|
||||
if meta == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "peer meta data was not provided")
|
||||
}
|
||||
peer, err := s.accountManager.AddPeer(req.GetSetupKey(), Peer{
|
||||
peer, err := s.accountManager.AddPeer(req.GetSetupKey(), &Peer{
|
||||
Key: peerKey.String(),
|
||||
Name: meta.GetHostname(),
|
||||
Meta: PeerSystemMeta{
|
||||
@@ -158,21 +158,22 @@ func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Pe
|
||||
return nil, status.Errorf(codes.NotFound, "provided setup key doesn't exists")
|
||||
}
|
||||
|
||||
peers, err := s.accountManager.GetPeersForAPeer(peer.Key)
|
||||
//todo move to AccountManager the code below
|
||||
networkMap, err := s.accountManager.GetNetworkMap(peer.Key)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "internal server error")
|
||||
}
|
||||
|
||||
// notify other peers of our registration
|
||||
for _, remotePeer := range peers {
|
||||
for _, remotePeer := range networkMap.Peers {
|
||||
// exclude notified peer and add ourselves
|
||||
peersToSend := []*Peer{peer}
|
||||
for _, p := range peers {
|
||||
for _, p := range networkMap.Peers {
|
||||
if remotePeer.Key != p.Key {
|
||||
peersToSend = append(peersToSend, p)
|
||||
}
|
||||
}
|
||||
update := toSyncResponse(s.config, peer, peersToSend, nil)
|
||||
update := toSyncResponse(s.config, peer, peersToSend, nil, networkMap.Network.Serial())
|
||||
err = s.peersUpdateManager.SendUpdate(remotePeer.Key, &UpdateMessage{Update: update})
|
||||
if err != nil {
|
||||
// todo rethink if we should keep this return
|
||||
@@ -317,7 +318,7 @@ func toRemotePeerConfig(peers []*Peer) []*proto.RemotePeerConfig {
|
||||
|
||||
}
|
||||
|
||||
func toSyncResponse(config *Config, peer *Peer, peers []*Peer, turnCredentials *TURNCredentials) *proto.SyncResponse {
|
||||
func toSyncResponse(config *Config, peer *Peer, peers []*Peer, turnCredentials *TURNCredentials, serial uint64) *proto.SyncResponse {
|
||||
|
||||
wtConfig := toWiretrusteeConfig(config, turnCredentials)
|
||||
|
||||
@@ -330,6 +331,12 @@ func toSyncResponse(config *Config, peer *Peer, peers []*Peer, turnCredentials *
|
||||
PeerConfig: pConfig,
|
||||
RemotePeers: remotePeers,
|
||||
RemotePeersIsEmpty: len(remotePeers) == 0,
|
||||
NetworkMap: &proto.NetworkMap{
|
||||
Serial: serial,
|
||||
PeerConfig: pConfig,
|
||||
RemotePeers: remotePeers,
|
||||
RemotePeersIsEmpty: len(remotePeers) == 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +348,7 @@ func (s *Server) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Empty,
|
||||
// sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization
|
||||
func (s *Server) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.ManagementService_SyncServer) error {
|
||||
|
||||
peers, err := s.accountManager.GetPeersForAPeer(peer.Key)
|
||||
networkMap, err := s.accountManager.GetNetworkMap(peer.Key)
|
||||
if err != nil {
|
||||
log.Warnf("error getting a list of peers for a peer %s", peer.Key)
|
||||
return err
|
||||
@@ -355,7 +362,7 @@ func (s *Server) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.Mana
|
||||
} else {
|
||||
turnCredentials = nil
|
||||
}
|
||||
plainResp := toSyncResponse(s.config, peer, peers, turnCredentials)
|
||||
plainResp := toSyncResponse(s.config, peer, networkMap.Peers, turnCredentials, networkMap.Network.Serial())
|
||||
|
||||
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
|
||||
if err != nil {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
//Peers is a handler that returns peers of the account
|
||||
type Peers struct {
|
||||
accountManager *server.AccountManager
|
||||
authAudience string
|
||||
}
|
||||
|
||||
//PeerResponse is a response sent to the client
|
||||
@@ -22,6 +23,7 @@ type PeerResponse struct {
|
||||
Connected bool
|
||||
LastSeen time.Time
|
||||
OS string
|
||||
Version string
|
||||
}
|
||||
|
||||
//PeerRequest is a request sent by the client
|
||||
@@ -29,9 +31,10 @@ type PeerRequest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func NewPeers(accountManager *server.AccountManager) *Peers {
|
||||
func NewPeers(accountManager *server.AccountManager, authAudience string) *Peers {
|
||||
return &Peers{
|
||||
accountManager: accountManager,
|
||||
authAudience: authAudience,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +64,24 @@ func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseW
|
||||
writeJSONObject(w, "")
|
||||
}
|
||||
|
||||
func (h *Peers) getPeerAccount(r *http.Request) (*server.Account, error) {
|
||||
jwtClaims := extractClaimsFromRequestContext(r, h.authAudience)
|
||||
|
||||
account, err := h.accountManager.GetAccountByUserOrAccountId(jwtClaims.UserId, jwtClaims.AccountId, jwtClaims.Domain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting account of a user %s: %v", jwtClaims.UserId, err)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||
accountId := extractAccountIdFromRequestContext(r)
|
||||
account, err := h.getPeerAccount(r)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
peerId := vars["id"] //effectively peer IP address
|
||||
if len(peerId) == 0 {
|
||||
@@ -70,7 +89,7 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
peer, err := h.accountManager.GetPeerByIP(accountId, peerId)
|
||||
peer, err := h.accountManager.GetPeerByIP(account.Id, peerId)
|
||||
if err != nil {
|
||||
http.Error(w, "peer not found", http.StatusNotFound)
|
||||
return
|
||||
@@ -78,10 +97,10 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodDelete:
|
||||
h.deletePeer(accountId, peer, w, r)
|
||||
h.deletePeer(account.Id, peer, w, r)
|
||||
return
|
||||
case http.MethodPut:
|
||||
h.updatePeer(accountId, peer, w, r)
|
||||
h.updatePeer(account.Id, peer, w, r)
|
||||
return
|
||||
case http.MethodGet:
|
||||
writeJSONObject(w, toPeerResponse(peer))
|
||||
@@ -96,11 +115,9 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
accountId := extractAccountIdFromRequestContext(r)
|
||||
//new user -> create a new account
|
||||
account, err := h.accountManager.GetOrCreateAccount(accountId)
|
||||
account, err := h.getPeerAccount(r)
|
||||
if err != nil {
|
||||
log.Errorf("failed getting user account %s: %v", accountId, err)
|
||||
log.Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -123,5 +140,6 @@ func toPeerResponse(peer *server.Peer) *PeerResponse {
|
||||
Connected: peer.Status.Connected,
|
||||
LastSeen: peer.Status.LastSeen,
|
||||
OS: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core),
|
||||
Version: peer.Meta.WtVersion,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net/http"
|
||||
@@ -14,6 +16,7 @@ import (
|
||||
// SetupKeys is a handler that returns a list of setup keys of the account
|
||||
type SetupKeys struct {
|
||||
accountManager *server.AccountManager
|
||||
authAudience string
|
||||
}
|
||||
|
||||
// SetupKeyResponse is a response sent to the client
|
||||
@@ -34,13 +37,14 @@ type SetupKeyResponse struct {
|
||||
type SetupKeyRequest struct {
|
||||
Name string
|
||||
Type server.SetupKeyType
|
||||
ExpiresIn Duration
|
||||
ExpiresIn *util.Duration
|
||||
Revoked bool
|
||||
}
|
||||
|
||||
func NewSetupKeysHandler(accountManager *server.AccountManager) *SetupKeys {
|
||||
func NewSetupKeysHandler(accountManager *server.AccountManager, authAudience string) *SetupKeys {
|
||||
return &SetupKeys{
|
||||
accountManager: accountManager,
|
||||
authAudience: authAudience,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +79,7 @@ func (h *SetupKeys) updateKey(accountId string, keyId string, w http.ResponseWri
|
||||
}
|
||||
|
||||
func (h *SetupKeys) getKey(accountId string, keyId string, w http.ResponseWriter, r *http.Request) {
|
||||
account, err := h.accountManager.GetAccount(accountId)
|
||||
account, err := h.accountManager.GetAccountById(accountId)
|
||||
if err != nil {
|
||||
http.Error(w, "account doesn't exist", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -102,7 +106,7 @@ func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.R
|
||||
return
|
||||
}
|
||||
|
||||
setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, req.Type, req.ExpiresIn.Duration)
|
||||
setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, req.Type, req.ExpiresIn)
|
||||
if err != nil {
|
||||
errStatus, ok := status.FromError(err)
|
||||
if ok && errStatus.Code() == codes.NotFound {
|
||||
@@ -116,8 +120,25 @@ func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.R
|
||||
writeSuccess(w, setupKey)
|
||||
}
|
||||
|
||||
func (h *SetupKeys) getSetupKeyAccount(r *http.Request) (*server.Account, error) {
|
||||
jwtClaims := extractClaimsFromRequestContext(r, h.authAudience)
|
||||
|
||||
account, err := h.accountManager.GetAccountByUserOrAccountId(jwtClaims.UserId, jwtClaims.AccountId, jwtClaims.Domain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting account of a user %s: %v", jwtClaims.UserId, err)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
||||
accountId := extractAccountIdFromRequestContext(r)
|
||||
account, err := h.getSetupKeyAccount(r)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
keyId := vars["id"]
|
||||
if len(keyId) == 0 {
|
||||
@@ -127,10 +148,10 @@ func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodPut:
|
||||
h.updateKey(accountId, keyId, w, r)
|
||||
h.updateKey(account.Id, keyId, w, r)
|
||||
return
|
||||
case http.MethodGet:
|
||||
h.getKey(accountId, keyId, w, r)
|
||||
h.getKey(account.Id, keyId, w, r)
|
||||
return
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
@@ -139,21 +160,18 @@ func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
accountId := extractAccountIdFromRequestContext(r)
|
||||
account, err := h.getSetupKeyAccount(r)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
h.createKey(accountId, w, r)
|
||||
h.createKey(account.Id, w, r)
|
||||
return
|
||||
case http.MethodGet:
|
||||
|
||||
//new user -> create a new account
|
||||
account, err := h.accountManager.GetOrCreateAccount(accountId)
|
||||
if err != nil {
|
||||
log.Errorf("failed getting user account %s: %v", accountId, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
@@ -164,7 +182,7 @@ func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
err = json.NewEncoder(w).Encode(respBody)
|
||||
if err != nil {
|
||||
log.Errorf("failed encoding account peers %s: %v", accountId, err)
|
||||
log.Errorf("failed encoding account peers %s: %v", account.Id, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,13 +8,28 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// extractAccountIdFromRequestContext extracts accountId from the request context previously filled by the JWT token (after auth)
|
||||
func extractAccountIdFromRequestContext(r *http.Request) string {
|
||||
// JWTClaims stores information from JWTs
|
||||
type JWTClaims struct {
|
||||
UserId string
|
||||
AccountId string
|
||||
Domain string
|
||||
}
|
||||
|
||||
// extractClaimsFromRequestContext extracts claims from the request context previously filled by the JWT token (after auth)
|
||||
func extractClaimsFromRequestContext(r *http.Request, authAudiance string) JWTClaims {
|
||||
token := r.Context().Value("user").(*jwt.Token)
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
|
||||
//actually a user id but for now we have a 1 to 1 mapping.
|
||||
return claims["sub"].(string)
|
||||
jwtClaims := JWTClaims{}
|
||||
jwtClaims.UserId = claims["sub"].(string)
|
||||
accountIdClaim, ok := claims[authAudiance+"wt_account_id"]
|
||||
if ok {
|
||||
jwtClaims.AccountId = accountIdClaim.(string)
|
||||
}
|
||||
domainClaim, ok := claims[authAudiance+"wt_user_domain"]
|
||||
if ok {
|
||||
jwtClaims.Domain = domainClaim.(string)
|
||||
}
|
||||
return jwtClaims
|
||||
}
|
||||
|
||||
//writeJSONObject simply writes object to the HTTP reponse in JSON format
|
||||
|
||||
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/cors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -17,10 +18,11 @@ 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)
|
||||
// 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{
|
||||
@@ -32,6 +34,18 @@ func NewHttpsServer(config *s.HttpServerConfig, certManager *autocert.Manager, a
|
||||
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)
|
||||
@@ -59,8 +73,8 @@ func (s *Server) Start() error {
|
||||
r := mux.NewRouter()
|
||||
r.Use(jwtMiddleware.Handler, corsMiddleware.Handler)
|
||||
|
||||
peersHandler := handler.NewPeers(s.accountManager)
|
||||
keysHandler := handler.NewSetupKeysHandler(s.accountManager)
|
||||
peersHandler := handler.NewPeers(s.accountManager, s.config.AuthAudience)
|
||||
keysHandler := handler.NewSetupKeysHandler(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")
|
||||
|
||||
@@ -71,13 +85,26 @@ func (s *Server) Start() error {
|
||||
if s.certManager != nil {
|
||||
// if HTTPS is enabled we reuse the listener from the cert manager
|
||||
listener := s.certManager.Listener()
|
||||
log.Infof("http server listening on %s", listener.Addr())
|
||||
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)
|
||||
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
|
||||
|
||||
207
management/server/idp/auth0.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Auth0Manager auth0 manager client instance
|
||||
type Auth0Manager struct {
|
||||
authIssuer string
|
||||
httpClient ManagerHTTPClient
|
||||
credentials ManagerCredentials
|
||||
helper ManagerHelper
|
||||
}
|
||||
|
||||
// Auth0ClientConfig auth0 manager client configurations
|
||||
type Auth0ClientConfig struct {
|
||||
Audience string `json:"audiance"`
|
||||
AuthIssuer string `json:"auth_issuer"`
|
||||
ClientId string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
GrantType string `json:"grant_type"`
|
||||
}
|
||||
|
||||
// Auth0Credentials auth0 authentication information
|
||||
type Auth0Credentials struct {
|
||||
clientConfig Auth0ClientConfig
|
||||
helper ManagerHelper
|
||||
httpClient ManagerHTTPClient
|
||||
jwtToken JWTToken
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
// NewAuth0Manager creates a new instance of the Auth0Manager
|
||||
func NewAuth0Manager(config Auth0ClientConfig) *Auth0Manager {
|
||||
|
||||
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
httpTransport.MaxIdleConns = 5
|
||||
httpTransport.IdleConnTimeout = 30
|
||||
|
||||
httpClient := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
Transport: httpTransport,
|
||||
}
|
||||
|
||||
helper := JsonParser{}
|
||||
|
||||
credentials := &Auth0Credentials{
|
||||
clientConfig: config,
|
||||
httpClient: httpClient,
|
||||
helper: helper,
|
||||
}
|
||||
return &Auth0Manager{
|
||||
authIssuer: config.AuthIssuer,
|
||||
credentials: credentials,
|
||||
httpClient: httpClient,
|
||||
helper: helper,
|
||||
}
|
||||
}
|
||||
|
||||
// jwtStillValid returns true if the token still valid and have enough time to be used and get a response from Auth0
|
||||
func (c *Auth0Credentials) jwtStillValid() bool {
|
||||
return !c.jwtToken.expiresInTime.IsZero() && time.Now().Add(5*time.Second).Before(c.jwtToken.expiresInTime)
|
||||
}
|
||||
|
||||
// requestJWTToken performs request to get jwt token
|
||||
func (c *Auth0Credentials) requestJWTToken() (*http.Response, error) {
|
||||
var res *http.Response
|
||||
url := c.clientConfig.AuthIssuer + "/oauth/token"
|
||||
|
||||
p, err := c.helper.Marshal(c.clientConfig)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
payload := strings.NewReader(string(p))
|
||||
|
||||
req, err := http.NewRequest("POST", url, payload)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
req.Header.Add("content-type", "application/json")
|
||||
|
||||
res, err = c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return res, fmt.Errorf("unable to get token, statusCode %d", res.StatusCode)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// parseRequestJWTResponse parses jwt raw response body and extracts token and expires in seconds
|
||||
func (c *Auth0Credentials) parseRequestJWTResponse(rawBody io.ReadCloser) (JWTToken, error) {
|
||||
jwtToken := JWTToken{}
|
||||
body, err := ioutil.ReadAll(rawBody)
|
||||
if err != nil {
|
||||
return jwtToken, err
|
||||
}
|
||||
|
||||
err = c.helper.Unmarshal(body, &jwtToken)
|
||||
if err != nil {
|
||||
return jwtToken, err
|
||||
}
|
||||
if jwtToken.ExpiresIn == 0 && jwtToken.AccessToken == "" {
|
||||
return jwtToken, fmt.Errorf("error while reading response body, expires_in: %d and access_token: %s", jwtToken.ExpiresIn, jwtToken.AccessToken)
|
||||
}
|
||||
data, err := jwt.DecodeSegment(strings.Split(jwtToken.AccessToken, ".")[1])
|
||||
if err != nil {
|
||||
return jwtToken, err
|
||||
}
|
||||
// Exp maps into exp from jwt token
|
||||
var IssuedAt struct{ Exp int64 }
|
||||
err = json.Unmarshal(data, &IssuedAt)
|
||||
if err != nil {
|
||||
return jwtToken, err
|
||||
}
|
||||
jwtToken.expiresInTime = time.Unix(IssuedAt.Exp, 0)
|
||||
|
||||
return jwtToken, nil
|
||||
}
|
||||
|
||||
// Authenticate retrieves access token to use the Auth0 Management API
|
||||
func (c *Auth0Credentials) Authenticate() (JWTToken, error) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
// If jwtToken has an expires time and we have enough time to do a request return immediately
|
||||
if c.jwtStillValid() {
|
||||
return c.jwtToken, nil
|
||||
}
|
||||
|
||||
res, err := c.requestJWTToken()
|
||||
if err != nil {
|
||||
return c.jwtToken, err
|
||||
}
|
||||
defer func() {
|
||||
err = res.Body.Close()
|
||||
if err != nil {
|
||||
log.Errorf("error while closing get jwt token response body: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
jwtToken, err := c.parseRequestJWTResponse(res.Body)
|
||||
if err != nil {
|
||||
return c.jwtToken, err
|
||||
}
|
||||
|
||||
c.jwtToken = jwtToken
|
||||
|
||||
return c.jwtToken, nil
|
||||
}
|
||||
|
||||
// UpdateUserAppMetadata updates user app metadata based on userId and metadata map
|
||||
func (am *Auth0Manager) UpdateUserAppMetadata(userId string, appMetadata AppMetadata) error {
|
||||
|
||||
jwtToken, err := am.credentials.Authenticate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url := am.authIssuer + "/api/v2/users/" + userId
|
||||
|
||||
data, err := am.helper.Marshal(appMetadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payloadString := fmt.Sprintf("{\"app_metadata\": %s}", string(data))
|
||||
|
||||
payload := strings.NewReader(payloadString)
|
||||
|
||||
req, err := http.NewRequest("PATCH", url, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
||||
req.Header.Add("content-type", "application/json")
|
||||
|
||||
res, err := am.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = res.Body.Close()
|
||||
if err != nil {
|
||||
log.Errorf("error while closing update user app metadata response body: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("unable to update the appMetadata, statusCode %d", res.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||