Compare commits
312 Commits
v0.0.6
...
revert-283
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c84ba73fb | ||
|
|
9ec4ea8e03 | ||
|
|
9c3cd1a5db | ||
|
|
347a668bd5 | ||
|
|
ef47385e38 | ||
|
|
3e46f38166 | ||
|
|
64e2e34dae | ||
|
|
8dd92f14bf | ||
|
|
071b03e790 | ||
|
|
3385ea6379 | ||
|
|
430e0415df | ||
|
|
b72ed91cb4 | ||
|
|
0b8387bd2c | ||
|
|
5d4c2643a3 | ||
|
|
69cda73bbb | ||
|
|
b29948b910 | ||
|
|
5f5cbf7e20 | ||
|
|
41c6af6b6f | ||
|
|
23fad49756 | ||
|
|
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 | ||
|
|
ec759bc461 | ||
|
|
a859f6c511 | ||
|
|
081162864d | ||
|
|
090f3ae5d0 | ||
|
|
fb1116e77b | ||
|
|
879750af7c | ||
|
|
13b4be31df | ||
|
|
15f7d856db | ||
|
|
72197d1970 | ||
|
|
a56aba8b06 | ||
|
|
4acbdc47e5 | ||
|
|
ee3c292699 | ||
|
|
6c233fcc3f | ||
|
|
a4db0b4e94 | ||
|
|
81c5aa1341 | ||
|
|
8c5f6186f1 | ||
|
|
88e9d2c20d | ||
|
|
9d76cf1ea7 | ||
|
|
737d4b5f2c | ||
|
|
4485124b67 | ||
|
|
b17424d630 | ||
|
|
86f3b1e5c8 | ||
|
|
a31cbb1f5b | ||
|
|
4f4edf8442 | ||
|
|
a78e518327 | ||
|
|
2c1d7c0fd4 | ||
|
|
593c66fea6 | ||
|
|
5f8211773d | ||
|
|
64ca05c8e7 | ||
|
|
307d41c08a | ||
|
|
5c7260298f | ||
|
|
7f7858b0a6 | ||
|
|
3c4b0b3a4b | ||
|
|
d4a24ac001 | ||
|
|
737866c149 | ||
|
|
49800a6d03 | ||
|
|
0fa15e6920 | ||
|
|
95845c88fe | ||
|
|
6869b48905 | ||
|
|
90ef1e939b | ||
|
|
bff137b109 | ||
|
|
2e9fc20567 | ||
|
|
617f79e2e0 | ||
|
|
d75353fbb8 | ||
|
|
b5a20bf1ba | ||
|
|
695148410f | ||
|
|
07ab9c196d | ||
|
|
4a5901ada1 | ||
|
|
4c427ae900 | ||
|
|
22fdb0a029 | ||
|
|
1b056ab75a | ||
|
|
3a41014adb | ||
|
|
708835afa8 | ||
|
|
8364e03944 | ||
|
|
0017360b8d | ||
|
|
9ace93d9fc | ||
|
|
b127e424f9 | ||
|
|
34cffb3bf0 | ||
|
|
02cc6a30f5 | ||
|
|
c68d9dff4a | ||
|
|
1dfa99d07c | ||
|
|
f7e51e7453 | ||
|
|
2c6748610c | ||
|
|
e8ca289f4a | ||
|
|
2a97053cae | ||
|
|
38e3c9c062 | ||
|
|
877ad97a96 | ||
|
|
80de6a75d5 | ||
|
|
dcc9dcacdc | ||
|
|
3c47a3c408 | ||
|
|
d5af5f1878 | ||
|
|
9f0c86c28e | ||
|
|
08d44b1d5f | ||
|
|
1f29975737 | ||
|
|
11982d6dde | ||
|
|
6ce5b2c815 | ||
|
|
ea99def502 | ||
|
|
f51a79d3b3 | ||
|
|
2c2c1e19df | ||
|
|
c0c4c4a266 | ||
|
|
3b30beb567 | ||
|
|
9e4aa4f1f1 | ||
|
|
83ac774264 | ||
|
|
2172d6f1b9 | ||
|
|
c98be683bf | ||
|
|
079d35eada | ||
|
|
d27eb317aa | ||
|
|
940578d600 | ||
|
|
1a8c03bef0 | ||
|
|
4e17890597 | ||
|
|
7b52049333 | ||
|
|
f9c3ed784f | ||
|
|
ea524e2a53 | ||
|
|
bffea0e145 | ||
|
|
2d85fcfcc3 | ||
|
|
07118d972d | ||
|
|
84f4d51c6c | ||
|
|
1e250fc0df | ||
|
|
d4a9f4d38a | ||
|
|
4587f7686e | ||
|
|
dd50f495ab | ||
|
|
bb2477491f | ||
|
|
f4d7faaf4e | ||
|
|
cffb08ad23 | ||
|
|
8d05789749 | ||
|
|
ca5970140f | ||
|
|
ac628b6efa | ||
|
|
80665049dc | ||
|
|
881f078759 | ||
|
|
1cf9b143e0 | ||
|
|
158547f3eb | ||
|
|
ab6452065d | ||
|
|
e553c5e97e | ||
|
|
3041ff4ef7 | ||
|
|
61a7f3013b | ||
|
|
dac865c61f | ||
|
|
a40669270a | ||
|
|
f2ca2fc7c1 | ||
|
|
729b16e599 | ||
|
|
561bd681d9 | ||
|
|
0e313eec24 | ||
|
|
4216cd2986 | ||
|
|
c18899d135 | ||
|
|
20248dadb7 | ||
|
|
1a06518f1b | ||
|
|
dd72a01ecf | ||
|
|
bbfbf797d5 | ||
|
|
52db303104 | ||
|
|
5122294adf | ||
|
|
a87f828844 | ||
|
|
8088c7a591 | ||
|
|
74355a2292 | ||
|
|
a66cdccda9 | ||
|
|
06c7af058b | ||
|
|
41b50a08d4 | ||
|
|
3c45da553a | ||
|
|
8dfccfc800 | ||
|
|
021092800b | ||
|
|
aa854c5899 | ||
|
|
e41fdedd5b | ||
|
|
923cabda9a | ||
|
|
db673ed34f | ||
|
|
6465e2556a | ||
|
|
89dba7951a | ||
|
|
9308a51800 | ||
|
|
94c0091a7b | ||
|
|
f247f9a2f8 | ||
|
|
c49bd23ac5 | ||
|
|
11174a50cd | ||
|
|
dfcf9f9087 | ||
|
|
5f8a489f90 | ||
|
|
9b9c7ada7d | ||
|
|
8b31088968 | ||
|
|
00f2ee34a0 | ||
|
|
51337fbf65 | ||
|
|
ca83e8c4a0 | ||
|
|
2784f6a098 | ||
|
|
6b5010f7d5 | ||
|
|
714c4c3c44 | ||
|
|
d5c4f6cb40 | ||
|
|
7df6cde968 | ||
|
|
744984861b | ||
|
|
83fe84d11a | ||
|
|
e059059e62 | ||
|
|
06b0c46a5d | ||
|
|
8acddfd510 | ||
|
|
caf2229d3b | ||
|
|
698ebe2287 | ||
|
|
54235f0a77 | ||
|
|
05168ae12f | ||
|
|
255ad7faa9 | ||
|
|
6e4c232ff2 | ||
|
|
59360519d6 | ||
|
|
3520b6471b | ||
|
|
74061597a3 | ||
|
|
33a98c7a2c | ||
|
|
9b327ea6ba | ||
|
|
45697a0000 | ||
|
|
884cd8dc55 | ||
|
|
f8eaf2f40e | ||
|
|
0609e1d75d | ||
|
|
8c9bc96c85 | ||
|
|
68112870dc | ||
|
|
ae69f4cf1b | ||
|
|
c8ad10d653 | ||
|
|
e622b2a529 | ||
|
|
44d5e7f205 | ||
|
|
790858c31b | ||
|
|
5342f10e7f | ||
|
|
f0048d16fb | ||
|
|
635cd2202d | ||
|
|
a773ec8150 | ||
|
|
84c6eb5e16 | ||
|
|
73720951d7 | ||
|
|
6d339295be | ||
|
|
f1cff0e13a | ||
|
|
e6358e7bb2 | ||
|
|
2337c3d84d |
40
.github/workflows/golang-test-build.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
name: Test Build On Platforms
|
||||||
|
on: [pull_request]
|
||||||
|
jobs:
|
||||||
|
test_build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ windows, linux, darwin ]
|
||||||
|
go-version: [1.17.x]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
|
- name: Cache Go modules
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-test-${{ matrix.os }}-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-test-${{ matrix.os }}
|
||||||
|
|
||||||
|
- name: Install modules
|
||||||
|
run: GOOS=${{ matrix.os }} go mod tidy
|
||||||
|
|
||||||
|
- name: run build client
|
||||||
|
run: GOOS=${{ matrix.os }} go build .
|
||||||
|
working-directory: client
|
||||||
|
|
||||||
|
- name: run build management
|
||||||
|
run: GOOS=${{ matrix.os }} go build .
|
||||||
|
working-directory: management
|
||||||
|
|
||||||
|
- name: run build signal
|
||||||
|
run: GOOS=${{ matrix.os }} go build .
|
||||||
|
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 ./...
|
||||||
12
.github/workflows/golangci-lint.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
name: golangci-lint
|
||||||
|
on: [pull_request]
|
||||||
|
jobs:
|
||||||
|
golangci:
|
||||||
|
name: lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
|
||||||
|
|
||||||
21
.github/workflows/release.yml
vendored
@@ -14,11 +14,15 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # It is required for GoReleaser to work properly
|
fetch-depth: 0 # It is required for GoReleaser to work properly
|
||||||
|
|
||||||
|
- name: Generate syso with DLL
|
||||||
|
run: bash -x wireguard_nt.sh
|
||||||
|
working-directory: client
|
||||||
-
|
-
|
||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.16
|
go-version: 1.17
|
||||||
-
|
-
|
||||||
name: Cache Go modules
|
name: Cache Go modules
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
@@ -49,4 +53,17 @@ jobs:
|
|||||||
version: latest
|
version: latest
|
||||||
args: release --rm-dist
|
args: release --rm-dist
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
|
||||||
|
UPLOAD_DEBIAN_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
|
||||||
|
UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
|
||||||
|
|
||||||
|
-
|
||||||
|
name: Trigger Windows binaries sign pipeline
|
||||||
|
uses: benc-uk/workflow-dispatch@v1
|
||||||
|
with:
|
||||||
|
workflow: Sign windows bin and installer
|
||||||
|
repo: wiretrustee/windows-sign-pipeline
|
||||||
|
ref: v0.0.2
|
||||||
|
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
||||||
|
inputs: '{ "tag": "${{ github.ref }}" }'
|
||||||
|
|||||||
10
.gitignore
vendored
@@ -1,2 +1,10 @@
|
|||||||
.idea
|
.idea
|
||||||
*.iml
|
*.iml
|
||||||
|
dist/
|
||||||
|
.env
|
||||||
|
conf.json
|
||||||
|
http-cmds.sh
|
||||||
|
infrastructure_files/management.json
|
||||||
|
infrastructure_files/docker-compose.yml
|
||||||
|
*.syso
|
||||||
|
client/.distfiles/
|
||||||
326
.goreleaser.yaml
@@ -1,39 +1,97 @@
|
|||||||
project_name: wiretrustee
|
project_name: wiretrustee
|
||||||
builds:
|
builds:
|
||||||
- env: [CGO_ENABLED=0]
|
- id: wiretrustee
|
||||||
|
dir: client
|
||||||
|
binary: wiretrustee
|
||||||
|
env: [CGO_ENABLED=0]
|
||||||
|
|
||||||
goos:
|
goos:
|
||||||
- linux
|
- linux
|
||||||
- darwin
|
- darwin
|
||||||
|
- windows
|
||||||
goarch:
|
goarch:
|
||||||
- arm
|
- arm
|
||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
|
- mips
|
||||||
|
gomips:
|
||||||
|
- hardfloat
|
||||||
|
- softfloat
|
||||||
ignore:
|
ignore:
|
||||||
- goos: darwin
|
- goos: windows
|
||||||
goarch: arm64
|
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_wgnt_from_rsrc
|
||||||
|
|
||||||
|
- id: wiretrustee-mgmt
|
||||||
|
dir: management
|
||||||
|
env: [CGO_ENABLED=0]
|
||||||
|
binary: wiretrustee-mgmt
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
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
|
||||||
|
env: [CGO_ENABLED=0]
|
||||||
|
binary: wiretrustee-signal
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
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:
|
nfpms:
|
||||||
- maintainer: Wiretrustee <wiretrustee@wiretrustee.com>
|
- maintainer: Wiretrustee <dev@wiretrustee.com>
|
||||||
description: Wiretrustee project.
|
description: Wiretrustee client.
|
||||||
homepage: https://wiretrustee.com/
|
homepage: https://wiretrustee.com/
|
||||||
|
id: deb
|
||||||
|
builds:
|
||||||
|
- wiretrustee
|
||||||
formats:
|
formats:
|
||||||
- deb
|
- 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:
|
scripts:
|
||||||
postinstall: "release_files/post_install.sh"
|
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:
|
dockers:
|
||||||
- image_templates:
|
- image_templates:
|
||||||
- wiretrustee/wiretrustee:signal-{{ .Version }}-amd64
|
- wiretrustee/wiretrustee:{{ .Version }}-amd64
|
||||||
|
ids:
|
||||||
|
- wiretrustee
|
||||||
goarch: amd64
|
goarch: amd64
|
||||||
use_buildx: true
|
use: buildx
|
||||||
dockerfile: Dockerfile
|
dockerfile: client/Dockerfile
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--platform=linux/amd64"
|
- "--platform=linux/amd64"
|
||||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||||
@@ -43,10 +101,150 @@ dockers:
|
|||||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||||
- image_templates:
|
- image_templates:
|
||||||
- wiretrustee/wiretrustee:signal-{{ .Version }}-arm64v8
|
- wiretrustee/wiretrustee:{{ .Version }}-arm64v8
|
||||||
|
ids:
|
||||||
|
- wiretrustee
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
use_buildx: true
|
use: buildx
|
||||||
dockerfile: Dockerfile
|
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:
|
||||||
|
- wiretrustee-signal
|
||||||
|
goarch: amd64
|
||||||
|
use: buildx
|
||||||
|
dockerfile: signal/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/signal:{{ .Version }}-arm64v8
|
||||||
|
ids:
|
||||||
|
- wiretrustee-signal
|
||||||
|
goarch: arm64
|
||||||
|
use: buildx
|
||||||
|
dockerfile: signal/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/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:
|
||||||
|
- wiretrustee-mgmt
|
||||||
|
goarch: amd64
|
||||||
|
use: buildx
|
||||||
|
dockerfile: management/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/management:{{ .Version }}-arm64v8
|
||||||
|
ids:
|
||||||
|
- wiretrustee-mgmt
|
||||||
|
goarch: arm64
|
||||||
|
use: buildx
|
||||||
|
dockerfile: management/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/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:
|
||||||
|
- wiretrustee-mgmt
|
||||||
|
goarch: amd64
|
||||||
|
use: buildx
|
||||||
|
dockerfile: management/Dockerfile.debug
|
||||||
|
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/management:{{ .Version }}-debug-arm64v8
|
||||||
|
ids:
|
||||||
|
- wiretrustee-mgmt
|
||||||
|
goarch: arm64
|
||||||
|
use: buildx
|
||||||
|
dockerfile: management/Dockerfile.debug
|
||||||
build_flag_templates:
|
build_flag_templates:
|
||||||
- "--platform=linux/arm64"
|
- "--platform=linux/arm64"
|
||||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||||
@@ -56,13 +254,93 @@ dockers:
|
|||||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
- "--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:
|
docker_manifests:
|
||||||
- name_template: wiretrustee/wiretrustee:signal-{{ .Version }}
|
- name_template: wiretrustee/wiretrustee:{{ .Version }}
|
||||||
image_templates:
|
image_templates:
|
||||||
- wiretrustee/wiretrustee:signal-{{ .Version }}-arm64v8
|
- wiretrustee/wiretrustee:{{ .Version }}-arm64v8
|
||||||
- wiretrustee/wiretrustee:signal-{{ .Version }}-amd64
|
- wiretrustee/wiretrustee:{{ .Version }}-arm
|
||||||
|
- wiretrustee/wiretrustee:{{ .Version }}-amd64
|
||||||
|
|
||||||
- name_template: wiretrustee/wiretrustee:signal-latest
|
- name_template: wiretrustee/wiretrustee:latest
|
||||||
image_templates:
|
image_templates:
|
||||||
- wiretrustee/wiretrustee:signal-{{ .Version }}-arm64v8
|
- wiretrustee/wiretrustee:{{ .Version }}-arm64v8
|
||||||
- wiretrustee/wiretrustee:signal-{{ .Version }}-amd64
|
- 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:
|
||||||
|
-
|
||||||
|
tap:
|
||||||
|
owner: wiretrustee
|
||||||
|
name: homebrew-client
|
||||||
|
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
|
||||||
|
commit_author:
|
||||||
|
name: Wiretrustee
|
||||||
|
email: wiretrustee@wiretrustee.com
|
||||||
|
description: Wiretrustee project.
|
||||||
|
download_strategy: CurlDownloadStrategy
|
||||||
|
homepage: https://wiretrustee.com/
|
||||||
|
license: "BSD3"
|
||||||
|
test: |
|
||||||
|
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)
|
Mikhail Bragin (https://github.com/braginini)
|
||||||
Maycon Santos (https://github.com/mlsmaycon)
|
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
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
FROM gcr.io/distroless/base:debug
|
|
||||||
EXPOSE 10000
|
|
||||||
ENTRYPOINT [ "/go/bin/wiretrustee","signal" ]
|
|
||||||
CMD ["--log-level","DEBUG"]
|
|
||||||
COPY wiretrustee /go/bin/wiretrustee
|
|
||||||
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
BSD 3-Clause License
|
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:
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
|||||||
256
README.md
@@ -1,83 +1,201 @@
|
|||||||
# 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>
|
||||||
|
|
||||||
### Why using Wiretrustee?
|
<p>
|
||||||
|
<a href="https://github.com/wiretrustee/wiretrustee/blob/main/LICENSE">
|
||||||
|
<img src="https://img.shields.io/badge/license-BSD--3-blue" />
|
||||||
|
</a>
|
||||||
|
<a href="https://hub.docker.com/r/wiretrustee/wiretrustee/tags">
|
||||||
|
<img src="https://img.shields.io/docker/pulls/wiretrustee/wiretrustee" />
|
||||||
|
</a>
|
||||||
|
<img src="https://badgen.net/badge/Open%20Source%3F/Yes%21/blue?icon=github" />
|
||||||
|
<br>
|
||||||
|
<a href="https://www.codacy.com/gh/wiretrustee/wiretrustee/dashboard?utm_source=github.com&utm_medium=referral&utm_content=wiretrustee/wiretrustee&utm_campaign=Badge_Grade"><img src="https://app.codacy.com/project/badge/Grade/d366de2c9d8b4cf982da27f8f5831809"/></a>
|
||||||
|
<a href="https://goreportcard.com/report/wiretrustee/wiretrustee">
|
||||||
|
<img src="https://goreportcard.com/badge/github.com/wiretrustee/wiretrustee?style=flat-square" />
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<a href="https://join.slack.com/t/wiretrustee/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A">
|
||||||
|
<img src="https://img.shields.io/badge/slack-@wiretrustee-red.svg?logo=slack"/>
|
||||||
|
</a>
|
||||||
|
</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 version:
|
||||||
|
[https://app.wiretrustee.com/](https://app.wiretrustee.com/peers).
|
||||||
|
|
||||||
|
[UI Dashboard Repo](https://github.com/wiretrustee/wiretrustee-dashboard)
|
||||||
|
|
||||||
* 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.
|
|
||||||
* 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.
|
|
||||||
* Works on ARM devices (e.g. Raspberry Pi).
|
|
||||||
|
|
||||||
### A bit on Wiretrustee internals
|
### A bit on Wiretrustee internals
|
||||||
|
* 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.
|
* 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.
|
||||||
* A connection session negotiation between peers is achieved with the Wiretrustee Signalling server [signal](signal/)
|
* Peers negotiate connection through [Signal Service](signal/).
|
||||||
* Contents of the messages sent between peers through the signaling server are encrypted with Wireguard keys, making it impossible to inspect them.
|
* Signal Service uses public Wireguard keys to route messages between peers.
|
||||||
The routing of the messages on a Signalling server is based on public Wireguard keys.
|
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).
|
* 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.
|
||||||
For that matter, there is support for a relay server fallback (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.
|
|
||||||
|
|
||||||
### What Wiretrustee is not doing:
|
<p float="left" align="middle">
|
||||||
* Wireguard key management. In consequence, you need to generate peer keys and specify them on Wiretrustee initialization step.
|
<img src="https://docs.wiretrustee.com/img/architecture/high-level-dia.png" width="700"/>
|
||||||
* Peer address management. You have to specify a unique peer local address (e.g. 10.30.30.1/24) when configuring Wiretrustee
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
### Product Roadmap
|
||||||
|
- [Public Roadmap](https://github.com/wiretrustee/wiretrustee/projects/2)
|
||||||
|
- [Public Roadmap Progress Tracking](https://github.com/wiretrustee/wiretrustee/projects/1)
|
||||||
|
|
||||||
### Client Installation
|
### Client Installation
|
||||||
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases)
|
#### Linux
|
||||||
2. Download the latest release:
|
|
||||||
```shell
|
**APT/Debian**
|
||||||
wget https://github.com/wiretrustee/wiretrustee/releases/download/v0.0.4/wiretrustee_0.0.4_linux_amd64.rpm
|
1. Add the repository:
|
||||||
```
|
```shell
|
||||||
3. Install the package
|
sudo apt-get update
|
||||||
```shell
|
sudo apt-get install ca-certificates curl gnupg -y
|
||||||
sudo dpkg -i wiretrustee_0.0.4_linux_amd64.deb
|
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
|
||||||
|
```
|
||||||
|
**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
|
||||||
|
```
|
||||||
|
3. Decompress
|
||||||
|
```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
|
||||||
|
````
|
||||||
|
|
||||||
|
#### 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.
|
||||||
|
> To uninstall the client and service, you can use Add/Remove programs
|
||||||
|
|
||||||
### Client Configuration
|
### Client Configuration
|
||||||
1. Initialize Wiretrustee:
|
1. Login to the Management Service. You need to have a `setup key` in hand (see ).
|
||||||
```shell
|
|
||||||
sudo wiretrustee init \
|
|
||||||
--stunURLs stun:stun.wiretrustee.com:3468,stun:stun.l.google.com:19302 \
|
|
||||||
--turnURLs <TURN User>:<TURN password>@turn:stun.wiretrustee.com:3468 \
|
|
||||||
--signalAddr signal.wiretrustee.com:10000 \
|
|
||||||
--wgLocalAddr 10.30.30.1/24 \
|
|
||||||
--log-level info
|
|
||||||
```
|
|
||||||
It is important to mention that the ```wgLocalAddr``` parameter has to be unique across your network.
|
|
||||||
E.g. if you have Peer A with ```wgLocalAddr=10.30.30.1/24``` then another Peer B can have ```wgLocalAddr=10.30.30.2/24```
|
|
||||||
|
|
||||||
If for some reason, you already have a generated Wireguard key, you can specify it with the ```--wgKey``` parameter.
|
For **Unix** systems:
|
||||||
If not specified, then a new one will be generated, and its corresponding public key will be output to the log.
|
```shell
|
||||||
A new config will be generated and stored under ```/etc/wiretrustee/config.json```
|
sudo wiretrustee up --setup-key <SETUP KEY>
|
||||||
|
```
|
||||||
2. Add a peer to connect to.
|
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
|
```shell
|
||||||
sudo wiretrustee add-peer --allowedIPs 10.30.30.2/32 --key '<REMOTE PEER WIREUARD PUBLIC KEY>'
|
docker run --network host --privileged --rm -d -e WT_SETUP_KEY=<SETUP KEY> -v wiretrustee-client:/etc/wiretrustee wiretrustee/wiretrustee:<TAG>
|
||||||
```
|
```
|
||||||
|
> 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 up --setup-key <SETUP KEY> --management-url https://localhost:33073
|
||||||
|
```
|
||||||
|
|
||||||
|
> You could also omit the `--setup-key` property. In this case, the tool will prompt for the key.
|
||||||
|
|
||||||
|
|
||||||
|
2. 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.
|
||||||
|
|
||||||
|
### Running Dashboard, Management, Signal and Coturn
|
||||||
|
See [Self-Hosting Guide](https://docs.wiretrustee.com/getting-started/self-hosting)
|
||||||
|
|
||||||
|
|
||||||
|
### Legal
|
||||||
|
[WireGuard](https://wireguard.com/) is a registered trademark of Jason A. Donenfeld.
|
||||||
|
|
||||||
3. Restart Wiretrustee to reload changes
|
|
||||||
```shell
|
|
||||||
sudo systemctl restart wiretrustee.service
|
|
||||||
sudo systemctl status wiretrustee.service
|
|
||||||
```
|
|
||||||
### Running the Signal service
|
|
||||||
After installing the application, you can run the signal using the command below:
|
|
||||||
````shell
|
|
||||||
/usr/local/bin/wiretrustee signal --log-level INFO
|
|
||||||
````
|
|
||||||
This will launch the signal service on port 10000, in case you want to change the port, use the flag --port.
|
|
||||||
#### Docker image
|
|
||||||
We have packed the signal into docker images. You can pull the images from the Docker Hub and execute it with the following commands:
|
|
||||||
````shell
|
|
||||||
docker pull wiretrustee/wiretrustee:signal-latest
|
|
||||||
docker run -d --name wiretrustee-signal -p 10000:10000 wiretrustee/wiretrustee:signal-latest
|
|
||||||
````
|
|
||||||
The default log-level is set to INFO, if you need you can change it using by updating the docker cmd as followed:
|
|
||||||
````shell
|
|
||||||
docker run -d --name wiretrustee-signal -p 10000:10000 wiretrustee/wiretrustee:signal-latest --log-level DEBUG
|
|
||||||
````
|
|
||||||
### Roadmap
|
|
||||||
* Android app
|
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
37
client/cmd/down.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var downCmd = &cobra.Command{
|
||||||
|
Use: "down",
|
||||||
|
Short: "down wiretrustee connections",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to connect to service CLI interface %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
daemonClient := proto.NewDaemonServiceClient(conn)
|
||||||
|
|
||||||
|
if _, err := daemonClient.Down(ctx, &proto.DownRequest{}); err != nil {
|
||||||
|
log.Errorf("call service down method: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
66
client/cmd/login.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var loginCmd = &cobra.Command{
|
||||||
|
Use: "login",
|
||||||
|
Short: "login to the Wiretrustee Management Service (first run)",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
ctx := internal.CtxInitState(context.Background())
|
||||||
|
|
||||||
|
// workaround to run without service
|
||||||
|
if logFile == "console" {
|
||||||
|
config, err := internal.GetConfig(managementURL, configPath, preSharedKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("get config file: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = WithBackOff(func() error {
|
||||||
|
return internal.Login(ctx, config, setupKey)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("backoff cycle failed: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if setupKey == "" {
|
||||||
|
log.Error("setup key can't be empty")
|
||||||
|
return fmt.Errorf("empty setup key")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to connect to service CLI interface %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := proto.LoginRequest{
|
||||||
|
SetupKey: setupKey,
|
||||||
|
PresharedKey: preSharedKey,
|
||||||
|
ManagementUrl: managementURL,
|
||||||
|
}
|
||||||
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
err = WithBackOff(func() error {
|
||||||
|
if _, err := client.Login(ctx, &request); err != nil {
|
||||||
|
log.Errorf("try login: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("backoff cycle failed: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
}
|
||||||
71
client/cmd/login_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||||
|
"github.com/wiretrustee/wiretrustee/iface"
|
||||||
|
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||||
|
"github.com/wiretrustee/wiretrustee/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mgmAddr string
|
||||||
|
|
||||||
|
func TestLogin_Start(t *testing.T) {
|
||||||
|
config := &mgmt.Config{}
|
||||||
|
_, err := util.ReadJson("../testdata/management.json", config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testDir := t.TempDir()
|
||||||
|
config.Datadir = testDir
|
||||||
|
err = util.CopyFileContents("../testdata/store.json", filepath.Join(testDir, "store.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, listener := startManagement(t, config)
|
||||||
|
mgmAddr = listener.Addr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogin(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
confPath := tempDir + "/config.json"
|
||||||
|
mgmtURL := fmt.Sprintf("http://%s", mgmAddr)
|
||||||
|
rootCmd.SetArgs([]string{
|
||||||
|
"login",
|
||||||
|
"--config",
|
||||||
|
confPath,
|
||||||
|
"--log-file",
|
||||||
|
"console",
|
||||||
|
"--setup-key",
|
||||||
|
strings.ToUpper("a2c8e62b-38f5-4553-b31e-dd66c696cebb"),
|
||||||
|
"--management-url",
|
||||||
|
mgmtURL,
|
||||||
|
})
|
||||||
|
err := rootCmd.Execute()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate generated config
|
||||||
|
actualConf := &internal.Config{}
|
||||||
|
_, err = util.ReadJson(confPath, actualConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected proper config file written, got broken %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualConf.ManagementURL.String() != mgmtURL {
|
||||||
|
t.Errorf("expected management URL %s got %s", mgmtURL, actualConf.ManagementURL.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualConf.WgIface != iface.WgInterfaceDefault {
|
||||||
|
t.Errorf("expected WgIfaceName %s got %s", iface.WgInterfaceDefault, actualConf.WgIface)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(actualConf.PrivateKey) == 0 {
|
||||||
|
t.Errorf("expected non empty Private key, got empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
146
client/cmd/root.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
configPath string
|
||||||
|
defaultConfigPath string
|
||||||
|
logLevel string
|
||||||
|
defaultLogFile string
|
||||||
|
logFile string
|
||||||
|
daemonAddr string
|
||||||
|
managementURL string
|
||||||
|
setupKey string
|
||||||
|
preSharedKey string
|
||||||
|
rootCmd = &cobra.Command{
|
||||||
|
Use: "wiretrustee",
|
||||||
|
Short: "",
|
||||||
|
Long: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution control channel for stopCh signal
|
||||||
|
stopCh chan int
|
||||||
|
cleanupCh chan struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Execute executes the root command.
|
||||||
|
func Execute() error {
|
||||||
|
return rootCmd.Execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
stopCh = make(chan int)
|
||||||
|
cleanupCh = make(chan struct{})
|
||||||
|
|
||||||
|
defaultConfigPath = "/etc/wiretrustee/config.json"
|
||||||
|
defaultLogFile = "/var/log/wiretrustee/client.log"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
defaultConfigPath = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "config.json"
|
||||||
|
defaultLogFile = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "client.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultDaemonAddr := "unix:///var/run/wiretrustee.sock"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
defaultDaemonAddr = "tcp://127.0.0.1:41731"
|
||||||
|
}
|
||||||
|
rootCmd.PersistentFlags().StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&managementURL, "management-url", "", fmt.Sprintf("Management Service URL [http|https]://[host]:[port] (default \"%s\")", internal.ManagementURLDefault().String()))
|
||||||
|
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(downCmd)
|
||||||
|
rootCmd.AddCommand(statusCmd)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupCloseHandler handles SIGTERM signal and exits with success
|
||||||
|
func SetupCloseHandler() {
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
for range c {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialClientGRPCServer returns client connection to the dameno server.
|
||||||
|
func DialClientGRPCServer(ctx context.Context, addr string) (*grpc.ClientConn, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, time.Second*3)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return grpc.DialContext(
|
||||||
|
ctx,
|
||||||
|
strings.TrimPrefix(addr, "tcp://"),
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
grpc.WithBlock(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBackOff execute function in backoff cycle.
|
||||||
|
func WithBackOff(bf func() error) error {
|
||||||
|
return backoff.RetryNotify(bf, CLIBackOffSettings, func(err error, duration time.Duration) {
|
||||||
|
log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLIBackOffSettings is default backoff settings for CLI commands.
|
||||||
|
var CLIBackOffSettings = &backoff.ExponentialBackOff{
|
||||||
|
InitialInterval: time.Second,
|
||||||
|
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||||
|
Multiplier: backoff.DefaultMultiplier,
|
||||||
|
MaxInterval: 10 * time.Second,
|
||||||
|
MaxElapsedTime: 30 * time.Second,
|
||||||
|
Stop: backoff.Stop,
|
||||||
|
Clock: backoff.SystemClock,
|
||||||
|
}
|
||||||
51
client/cmd/service.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/kardianos/service"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type program struct {
|
||||||
|
ctx context.Context
|
||||||
|
cmd *cobra.Command
|
||||||
|
args []string
|
||||||
|
serv *grpc.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProgram(cmd *cobra.Command, args []string) *program {
|
||||||
|
ctx := internal.CtxInitState(cmd.Context())
|
||||||
|
return &program{
|
||||||
|
ctx: ctx,
|
||||||
|
cmd: cmd,
|
||||||
|
args: args,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSVCConfig() *service.Config {
|
||||||
|
return &service.Config{
|
||||||
|
Name: "wiretrustee",
|
||||||
|
DisplayName: "Wiretrustee",
|
||||||
|
Description: "A WireGuard-based mesh network that connects your devices into a single private network.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSVC(prg *program, conf *service.Config) (service.Service, error) {
|
||||||
|
s, err := service.New(prg, conf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceCmd = &cobra.Command{
|
||||||
|
Use: "service",
|
||||||
|
Short: "manages wiretrustee service",
|
||||||
|
}
|
||||||
|
|
||||||
181
client/cmd/service_controller.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kardianos/service"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/server"
|
||||||
|
"github.com/wiretrustee/wiretrustee/util"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *program) Start(svc service.Service) error {
|
||||||
|
// Start should not block. Do the actual work async.
|
||||||
|
log.Info("starting service") //nolint
|
||||||
|
go func() {
|
||||||
|
// in any case, even if configuration does not exists we run daemon to serve CLI gRPC API.
|
||||||
|
p.serv = grpc.NewServer()
|
||||||
|
|
||||||
|
split := strings.Split(daemonAddr, "://")
|
||||||
|
switch split[0] {
|
||||||
|
case "unix":
|
||||||
|
// cleanup failed close
|
||||||
|
stat, err := os.Stat(split[1])
|
||||||
|
if err == nil && !stat.IsDir() {
|
||||||
|
if err := os.Remove(split[1]); err != nil {
|
||||||
|
log.Debugf("remove socket file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "tcp":
|
||||||
|
default:
|
||||||
|
log.Errorf("unsupported daemon address protocol: %v", split[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
listen, err := net.Listen(split[0], split[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to listen daemon interface: %v", err)
|
||||||
|
}
|
||||||
|
defer listen.Close()
|
||||||
|
|
||||||
|
serverInstance := server.New(p.ctx, managementURL, configPath, stopCh, cleanupCh)
|
||||||
|
if err := serverInstance.Start(); err != nil {
|
||||||
|
log.Fatalf("failed start daemon: %v", err)
|
||||||
|
}
|
||||||
|
proto.RegisterDaemonServiceServer(p.serv, serverInstance)
|
||||||
|
|
||||||
|
log.Printf("started daemon server: %v", split[1])
|
||||||
|
if err := p.serv.Serve(listen); err != nil {
|
||||||
|
log.Errorf("failed to serve daemon requests: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *program) Stop(service.Service) error {
|
||||||
|
go func() {
|
||||||
|
stopCh <- 1
|
||||||
|
}()
|
||||||
|
|
||||||
|
// stop CLI daemon service
|
||||||
|
if p.serv != nil {
|
||||||
|
p.serv.GracefulStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var runCmd = &cobra.Command{
|
||||||
|
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()
|
||||||
|
|
||||||
|
s, err := newSVC(newProgram(cmd, args), newSVCConfig())
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.Run()
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd.Printf("Wiretrustee service is running")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var startCmd = &cobra.Command{
|
||||||
|
Use: "start",
|
||||||
|
Short: "starts wiretrustee service",
|
||||||
|
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(newProgram(cmd, args), newSVCConfig())
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.Start()
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.Println("Wiretrustee service has been started")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var stopCmd = &cobra.Command{
|
||||||
|
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(newProgram(cmd, args), newSVCConfig())
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.Stop()
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd.Println("Wiretrustee service has been stopped")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var restartCmd = &cobra.Command{
|
||||||
|
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(newProgram(cmd, args), newSVCConfig())
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.Restart()
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd.Println("Wiretrustee service has been restarted")
|
||||||
|
},
|
||||||
|
}
|
||||||
67
client/cmd/service_installer.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var installCmd = &cobra.Command{
|
||||||
|
Use: "install",
|
||||||
|
Short: "installs wiretrustee service",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
|
||||||
|
svcConfig := newSVCConfig()
|
||||||
|
|
||||||
|
svcConfig.Arguments = []string{
|
||||||
|
"service",
|
||||||
|
"run",
|
||||||
|
"--config",
|
||||||
|
configPath,
|
||||||
|
"--log-level",
|
||||||
|
logLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
// Respected only by systemd systems
|
||||||
|
svcConfig.Dependencies = []string{"After=network.target syslog.target"}
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := newSVC(newProgram(cmd, args), svcConfig)
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.Install()
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.Println("Wiretrustee service has been installed")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var uninstallCmd = &cobra.Command{
|
||||||
|
Use: "uninstall",
|
||||||
|
Short: "uninstalls wiretrustee service from system",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
|
||||||
|
s, err := newSVC(newProgram(cmd, args), newSVCConfig())
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.Uninstall()
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd.Println("Wiretrustee has been uninstalled")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
37
client/cmd/status.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var statusCmd = &cobra.Command{
|
||||||
|
Use: "status",
|
||||||
|
Short: "status of the Wiretrustee Service",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
ctx := internal.CtxInitState(context.Background())
|
||||||
|
|
||||||
|
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to connect to service CLI interface %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
resp, err := proto.NewDaemonServiceClient(conn).Status(cmd.Context(), &proto.StatusRequest{})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("status failed: %v", status.Convert(err).Message())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("status: %v", resp.Status)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
92
client/cmd/testutil.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
clientProto "github.com/wiretrustee/wiretrustee/client/proto"
|
||||||
|
client "github.com/wiretrustee/wiretrustee/client/server"
|
||||||
|
mgmtProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||||
|
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||||
|
sigProto "github.com/wiretrustee/wiretrustee/signal/proto"
|
||||||
|
sig "github.com/wiretrustee/wiretrustee/signal/server"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func startSignal(t *testing.T) (*grpc.Server, net.Listener) {
|
||||||
|
lis, err := net.Listen("tcp", ":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s := grpc.NewServer()
|
||||||
|
sigProto.RegisterSignalExchangeServer(s, sig.NewServer())
|
||||||
|
go func() {
|
||||||
|
if err := s.Serve(lis); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return s, lis
|
||||||
|
}
|
||||||
|
|
||||||
|
func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Listener) {
|
||||||
|
lis, err := net.Listen("tcp", ":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s := grpc.NewServer()
|
||||||
|
store, err := mgmt.NewStore(config.Datadir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
||||||
|
accountManager := mgmt.NewManager(store, peersUpdateManager, nil)
|
||||||
|
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
|
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
mgmtProto.RegisterManagementServiceServer(s, mgmtServer)
|
||||||
|
go func() {
|
||||||
|
if err := s.Serve(lis); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return s, lis
|
||||||
|
}
|
||||||
|
|
||||||
|
func startClientDaemon(
|
||||||
|
t *testing.T, ctx context.Context, managementURL, configPath string,
|
||||||
|
stopCh chan int, cleanupCh chan<- struct{},
|
||||||
|
) (*grpc.Server, net.Listener) {
|
||||||
|
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s := grpc.NewServer()
|
||||||
|
|
||||||
|
server := client.New(
|
||||||
|
ctx,
|
||||||
|
managementURL,
|
||||||
|
configPath,
|
||||||
|
stopCh,
|
||||||
|
cleanupCh,
|
||||||
|
)
|
||||||
|
if err := server.Start(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
clientProto.RegisterDaemonServiceServer(s, server)
|
||||||
|
go func() {
|
||||||
|
if err := s.Serve(lis); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
return s, lis
|
||||||
|
}
|
||||||
78
client/cmd/up.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var upCmd = &cobra.Command{
|
||||||
|
Use: "up",
|
||||||
|
Short: "install, login and start wiretrustee client",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
ctx := internal.CtxInitState(cmd.Context())
|
||||||
|
|
||||||
|
// workaround to run without service
|
||||||
|
if logFile == "console" {
|
||||||
|
config, err := internal.GetConfig(managementURL, configPath, preSharedKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("get config file: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = WithBackOff(func() error {
|
||||||
|
return internal.Login(ctx, config, setupKey)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("backoff cycle failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
SetupCloseHandler()
|
||||||
|
return internal.RunClient(ctx, config, stopCh, cleanupCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to connect to service CLI interface %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
daemonClient := proto.NewDaemonServiceClient(conn)
|
||||||
|
|
||||||
|
loginRequest := proto.LoginRequest{
|
||||||
|
SetupKey: setupKey,
|
||||||
|
PresharedKey: preSharedKey,
|
||||||
|
ManagementUrl: managementURL,
|
||||||
|
}
|
||||||
|
err = WithBackOff(func() error {
|
||||||
|
_, err := daemonClient.Login(ctx, &loginRequest)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("backoff cycle failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := daemonClient.Status(ctx, &proto.StatusRequest{})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("get status: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.Status != string(internal.StatusIdle) {
|
||||||
|
log.Warnf("already connected")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := daemonClient.Up(ctx, &proto.UpRequest{}); err != nil {
|
||||||
|
log.Errorf("call service up method: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
98
client/cmd/up_daemon_test.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||||
|
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||||
|
"github.com/wiretrustee/wiretrustee/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpDaemon_Start(t *testing.T) {
|
||||||
|
config := &mgmt.Config{}
|
||||||
|
_, err := util.ReadJson("../testdata/management.json", config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testDir := t.TempDir()
|
||||||
|
config.Datadir = testDir
|
||||||
|
err = util.CopyFileContents("../testdata/store.json", filepath.Join(testDir, "store.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, signalLis := startSignal(t)
|
||||||
|
signalAddr = signalLis.Addr().String()
|
||||||
|
config.Signal.URI = signalAddr
|
||||||
|
|
||||||
|
_, mgmLis := startManagement(t, config)
|
||||||
|
mgmAddr = mgmLis.Addr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpDaemon(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
confPath := tempDir + "/config.json"
|
||||||
|
|
||||||
|
stopCh = make(chan int, 1)
|
||||||
|
cleanupCh = make(chan struct{}, 1)
|
||||||
|
|
||||||
|
ctx := internal.CtxInitState(context.Background())
|
||||||
|
state := internal.CtxGetState(ctx)
|
||||||
|
|
||||||
|
_, cliLis := startClientDaemon(t, ctx, "http://"+mgmAddr, confPath, stopCh, cleanupCh)
|
||||||
|
|
||||||
|
cliAddr = cliLis.Addr().String()
|
||||||
|
|
||||||
|
daemonAddr = "tcp://" + cliAddr
|
||||||
|
rootCmd.SetArgs([]string{
|
||||||
|
"login",
|
||||||
|
"--daemon-addr", "tcp://" + cliAddr,
|
||||||
|
"--setup-key", "A2C8E62B-38F5-4553-B31E-DD66C696CEBB",
|
||||||
|
"--log-file", "",
|
||||||
|
})
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
t.Errorf("expected no error while running up command, got %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
if status, err := state.Status(); err != nil && status != internal.StatusIdle {
|
||||||
|
t.Errorf("wrong status after login: %s, %v", internal.StatusIdle, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.SetArgs([]string{
|
||||||
|
"up",
|
||||||
|
"--daemon-addr", "tcp://" + cliAddr,
|
||||||
|
"--log-file", "",
|
||||||
|
})
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
t.Errorf("expected no error while running up command, got %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
if status, err := state.Status(); err != nil && status != internal.StatusConnected {
|
||||||
|
t.Errorf("wrong status after connect: %s, %v", status, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.SetArgs([]string{
|
||||||
|
"status",
|
||||||
|
"--daemon-addr", "tcp://" + cliAddr,
|
||||||
|
})
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
t.Errorf("expected no error while running up command, got %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
|
||||||
|
rootCmd.SetErr(nil)
|
||||||
|
rootCmd.SetArgs([]string{"down", "--daemon-addr", "tcp://" + cliAddr})
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
t.Errorf("expected no error while running up command, got %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// we can't check status here, because context already canceled
|
||||||
|
}
|
||||||
88
client/cmd/up_test.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wiretrustee/wiretrustee/iface"
|
||||||
|
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||||
|
"github.com/wiretrustee/wiretrustee/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
signalAddr string
|
||||||
|
cliAddr string
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUp_Start(t *testing.T) {
|
||||||
|
config := &mgmt.Config{}
|
||||||
|
_, err := util.ReadJson("../testdata/management.json", config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testDir := t.TempDir()
|
||||||
|
config.Datadir = testDir
|
||||||
|
err = util.CopyFileContents("../testdata/store.json", filepath.Join(testDir, "store.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, signalLis := startSignal(t)
|
||||||
|
signalAddr = signalLis.Addr().String()
|
||||||
|
config.Signal.URI = signalAddr
|
||||||
|
|
||||||
|
_, mgmLis := startManagement(t, config)
|
||||||
|
mgmAddr = mgmLis.Addr().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUp(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
confPath := tempDir + "/config.json"
|
||||||
|
mgmtURL, err := url.Parse("http://" + mgmAddr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.SetArgs([]string{
|
||||||
|
"up",
|
||||||
|
"--config",
|
||||||
|
confPath,
|
||||||
|
"--setup-key",
|
||||||
|
"A2C8E62B-38F5-4553-B31E-DD66C696CEBB",
|
||||||
|
"--management-url",
|
||||||
|
mgmtURL.String(),
|
||||||
|
"--log-level",
|
||||||
|
"debug",
|
||||||
|
"--log-file",
|
||||||
|
"console",
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
t.Errorf("expected no error while running up command, got %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
119
client/installer.nsis
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
!define APP_NAME "Wiretrustee"
|
||||||
|
!define COMP_NAME "Wiretrustee"
|
||||||
|
!define WEB_SITE "wiretrustee.com"
|
||||||
|
!define VERSION $%APPVER%
|
||||||
|
!define COPYRIGHT "Wiretrustee Authors, 2021"
|
||||||
|
!define DESCRIPTION "A WireGuard®-based mesh network that connects your devices into a single private network"
|
||||||
|
!define INSTALLER_NAME "wiretrustee-installer.exe"
|
||||||
|
!define MAIN_APP_EXE "Wiretrustee"
|
||||||
|
!define ICON "ui\\wiretrustee.ico"
|
||||||
|
!define BANNER "ui\\banner.bmp"
|
||||||
|
!define LICENSE_DATA "..\\LICENSE"
|
||||||
|
|
||||||
|
!define INSTALL_DIR "$PROGRAMFILES64\${APP_NAME}"
|
||||||
|
!define INSTALL_TYPE "SetShellVarContext all"
|
||||||
|
!define REG_ROOT "HKLM"
|
||||||
|
!define REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\${MAIN_APP_EXE}"
|
||||||
|
!define UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}"
|
||||||
|
Unicode True
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
VIProductVersion "${VERSION}"
|
||||||
|
VIAddVersionKey "ProductName" "${APP_NAME}"
|
||||||
|
VIAddVersionKey "CompanyName" "${COMP_NAME}"
|
||||||
|
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
|
||||||
|
VIAddVersionKey "FileDescription" "${DESCRIPTION}"
|
||||||
|
VIAddVersionKey "FileVersion" "${VERSION}"
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
SetCompressor /SOLID Lzma
|
||||||
|
Name "${APP_NAME}"
|
||||||
|
Caption "${APP_NAME}"
|
||||||
|
OutFile "..\\${INSTALLER_NAME}"
|
||||||
|
BrandingText "${APP_NAME}"
|
||||||
|
InstallDirRegKey "${REG_ROOT}" "${REG_APP_PATH}" ""
|
||||||
|
InstallDir "${INSTALL_DIR}"
|
||||||
|
LicenseData "${LICENSE_DATA}"
|
||||||
|
ShowInstDetails Show
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
!define MUI_ICON "${ICON}"
|
||||||
|
!define MUI_UNICON "${ICON}"
|
||||||
|
!define MUI_WELCOMEFINISHPAGE_BITMAP "${BANNER}"
|
||||||
|
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "${BANNER}"
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
!include "MUI2.nsh"
|
||||||
|
|
||||||
|
!define MUI_ABORTWARNING
|
||||||
|
!define MUI_UNABORTWARNING
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_LICENSE "${LICENSE_DATA}"
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
|
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
|
|
||||||
|
!insertmacro MUI_UNPAGE_FINISH
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGUAGE "English"
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
Section -MainProgram
|
||||||
|
${INSTALL_TYPE}
|
||||||
|
SetOverwrite ifnewer
|
||||||
|
SetOutPath "$INSTDIR"
|
||||||
|
File /r "..\\dist\\wiretrustee_windows_amd64\\"
|
||||||
|
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
Section -Icons_Reg
|
||||||
|
SetOutPath "$INSTDIR"
|
||||||
|
WriteUninstaller "$INSTDIR\wiretrustee_uninstall.exe"
|
||||||
|
|
||||||
|
WriteRegStr ${REG_ROOT} "${REG_APP_PATH}" "" "$INSTDIR\${MAIN_APP_EXE}"
|
||||||
|
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayName" "${APP_NAME}"
|
||||||
|
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "UninstallString" "$INSTDIR\wiretrustee_uninstall.exe"
|
||||||
|
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayIcon" "$INSTDIR\${MAIN_APP_EXE}"
|
||||||
|
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayVersion" "${VERSION}"
|
||||||
|
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "Publisher" "${COMP_NAME}"
|
||||||
|
|
||||||
|
EnVar::SetHKLM
|
||||||
|
EnVar::AddValueEx "path" "$INSTDIR"
|
||||||
|
|
||||||
|
Exec '"$INSTDIR\${MAIN_APP_EXE}" service install'
|
||||||
|
# sleep a bit for visibility
|
||||||
|
Sleep 1000
|
||||||
|
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
|
||||||
|
RmDir /r "$INSTDIR"
|
||||||
|
|
||||||
|
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
|
||||||
|
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
|
||||||
|
EnVar::SetHKLM
|
||||||
|
EnVar::DeleteValue "path" "$INSTDIR"
|
||||||
|
SectionEnd
|
||||||
118
client/internal/config.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/wiretrustee/wiretrustee/iface"
|
||||||
|
"github.com/wiretrustee/wiretrustee/util"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var managementURLDefault *url.URL
|
||||||
|
|
||||||
|
func ManagementURLDefault() *url.URL {
|
||||||
|
return managementURLDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
managementURL, err := parseManagementURL("https://api.wiretrustee.com:33073")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
managementURLDefault = managementURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config Configuration type
|
||||||
|
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, preSharedKey string) (*Config, error) {
|
||||||
|
wgKey := generateKey()
|
||||||
|
config := &Config{PrivateKey: wgKey, WgIface: iface.WgInterfaceDefault, IFaceBlackList: []string{}}
|
||||||
|
if managementURL != "" {
|
||||||
|
URL, err := parseManagementURL(managementURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.ManagementURL = URL
|
||||||
|
} else {
|
||||||
|
config.ManagementURL = managementURLDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
if preSharedKey != "" {
|
||||||
|
config.PreSharedKey = preSharedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
config.IFaceBlackList = []string{iface.WgInterfaceDefault, "tun0"}
|
||||||
|
|
||||||
|
err := util.WriteJson(configPath, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseManagementURL(managementURL string) (*url.URL, error) {
|
||||||
|
|
||||||
|
parsedMgmtURL, err := url.ParseRequestURI(managementURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed parsing management URL %s: [%s]", managementURL, err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(parsedMgmtURL.Scheme == "https" || parsedMgmtURL.Scheme == "http") {
|
||||||
|
return nil, fmt.Errorf("invalid Management Service URL provided %s. Supported format [http|https]://[host]:[port]", managementURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedMgmtURL, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadConfig reads existing config. In case provided managementURL is not empty overrides the read property
|
||||||
|
func ReadConfig(managementURL string, configPath string) (*Config, error) {
|
||||||
|
config := &Config{}
|
||||||
|
_, err := util.ReadJson(configPath, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if managementURL != "" {
|
||||||
|
URL, err := parseManagementURL(managementURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.ManagementURL = URL
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig reads existing config or generates a new one
|
||||||
|
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, preSharedKey)
|
||||||
|
} else {
|
||||||
|
return ReadConfig(managementURL, configPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateKey generates a new Wireguard private key
|
||||||
|
func generateKey() string {
|
||||||
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return key.String()
|
||||||
|
}
|
||||||
210
client/internal/connect.go
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"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"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunClient with main logic.
|
||||||
|
func RunClient(
|
||||||
|
ctx context.Context, config *Config, stopCh <-chan int, cleanupCh chan<- struct{},
|
||||||
|
) error {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
state := CtxGetState(ctx)
|
||||||
|
defer state.Set(StatusIdle)
|
||||||
|
|
||||||
|
wrapErr := state.Wrap
|
||||||
|
operation := func() error {
|
||||||
|
// if context cancelled we not start new backoff cycle
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Set(StatusConnecting)
|
||||||
|
// validate our peer's Wireguard PRIVATE key
|
||||||
|
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mgmTlsEnabled bool
|
||||||
|
if config.ManagementURL.Scheme == "https" {
|
||||||
|
mgmTlsEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 wrapErr(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 wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peerConfig := loginResp.GetPeerConfig()
|
||||||
|
|
||||||
|
engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
engine := NewEngine(ctx, cancel, signalClient, mgmClient, engineConfig)
|
||||||
|
err = engine.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error while starting Wiretrustee Connection Engine: %s", err)
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print("Wiretrustee engine started, my IP is: ", peerConfig.Address)
|
||||||
|
state.Set(StatusConnected)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-stopCh:
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
|
||||||
|
backOff.Reset()
|
||||||
|
|
||||||
|
err = mgmClient.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed closing Management Service client %v", err)
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
err = signalClient.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed closing Signal Service client %v", err)
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = engine.Stop()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed stopping engine %v", err)
|
||||||
|
return wrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
cleanupCh <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Info("stopped Wiretrustee client")
|
||||||
|
|
||||||
|
if _, err := state.Status(); err == ErrResetConnection {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := backoff.Retry(operation, backOff)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("exiting client retry loop due to unrecoverable error: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createEngineConfig converts configuration received from Management Service to EngineConfig
|
||||||
|
func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.PeerConfig) (*EngineConfig, error) {
|
||||||
|
iFaceBlackList := make(map[string]struct{})
|
||||||
|
for i := 0; i < len(config.IFaceBlackList); i += 2 {
|
||||||
|
iFaceBlackList[config.IFaceBlackList[i]] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
engineConf := &EngineConfig{
|
||||||
|
WgIfaceName: config.WgIface,
|
||||||
|
WgAddr: peerConfig.Address,
|
||||||
|
IFaceBlackList: iFaceBlackList,
|
||||||
|
WgPrivateKey: key,
|
||||||
|
WgPort: iface.DefaultWgPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.GrpcClient, error) {
|
||||||
|
var sigTLSEnabled bool
|
||||||
|
if wtConfig.Signal.Protocol == mgmProto.HostConfig_HTTPS {
|
||||||
|
sigTLSEnabled = true
|
||||||
|
} else {
|
||||||
|
sigTLSEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
signalClient, err := signal.NewClient(ctx, wtConfig.Signal.Uri, ourPrivateKey, sigTLSEnabled)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error while connecting to the Signal Exchange Service %s: %s", wtConfig.Signal.Uri, err)
|
||||||
|
return nil, status.Errorf(codes.FailedPrecondition, "failed connecting to Signal Service : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return signalClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connectToManagement creates Management Services client, establishes a connection, logs-in and gets a global Wiretrustee config (signal, turn, stun hosts, etc)
|
||||||
|
func connectToManagement(ctx context.Context, managementAddr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*mgm.GrpcClient, *mgmProto.LoginResponse, error) {
|
||||||
|
log.Debugf("connecting to management server %s", managementAddr)
|
||||||
|
client, err := mgm.NewClient(ctx, managementAddr, ourPrivateKey, tlsEnabled)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, status.Errorf(codes.FailedPrecondition, "failed connecting to Management Service : %s", err)
|
||||||
|
}
|
||||||
|
log.Debugf("connected to management server %s", managementAddr)
|
||||||
|
|
||||||
|
serverPublicKey, err := client.GetServerPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, status.Errorf(codes.FailedPrecondition, "failed while getting Management Service public key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loginResp, err := client.Login(*serverPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
||||||
|
log.Error("peer registration required. Please run wiretrustee login command first")
|
||||||
|
return nil, nil, err
|
||||||
|
} else {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("peer logged in to Management Service %s", managementAddr)
|
||||||
|
|
||||||
|
return client, loginResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
628
client/internal/engine.go
Normal file
@@ -0,0 +1,628 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrResetConnection = fmt.Errorf("reset connection")
|
||||||
|
|
||||||
|
// EngineConfig is a config for the Engine
|
||||||
|
type EngineConfig struct {
|
||||||
|
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
|
||||||
|
// mgmClient is a Management Service client
|
||||||
|
mgmClient mgm.Client
|
||||||
|
// peerConns is a map that holds all the peers that are known to this peer
|
||||||
|
peerConns map[string]*peer.Conn
|
||||||
|
|
||||||
|
// syncMsgMux is used to guarantee sequential Management Service message processing
|
||||||
|
syncMsgMux *sync.Mutex
|
||||||
|
|
||||||
|
config *EngineConfig
|
||||||
|
// 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
|
||||||
|
type Peer struct {
|
||||||
|
WgPubKey string
|
||||||
|
WgAllowedIps string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEngine creates a new Connection Engine
|
||||||
|
func NewEngine(
|
||||||
|
ctx context.Context, cancel context.CancelFunc,
|
||||||
|
signalClient signal.Client, mgmClient mgm.Client, config *EngineConfig,
|
||||||
|
) *Engine {
|
||||||
|
return &Engine{
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
signal: signalClient,
|
||||||
|
mgmClient: mgmClient,
|
||||||
|
peerConns: map[string]*peer.Conn{},
|
||||||
|
syncMsgMux: &sync.Mutex{},
|
||||||
|
config: config,
|
||||||
|
STUNs: []*ice.URL{},
|
||||||
|
TURNs: []*ice.URL{},
|
||||||
|
networkSerial: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Engine) Stop() error {
|
||||||
|
e.syncMsgMux.Lock()
|
||||||
|
defer e.syncMsgMux.Unlock()
|
||||||
|
|
||||||
|
err := e.removeAllPeers()
|
||||||
|
if err != nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start creates a new Wireguard tunnel interface and listens to events from Signal and Management services
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
wgIfaceName := e.config.WgIfaceName
|
||||||
|
wgAddr := e.config.WgAddr
|
||||||
|
myPrivateKey := e.config.WgPrivateKey
|
||||||
|
var err error
|
||||||
|
|
||||||
|
e.wgInterface, err = iface.NewWGIface(wgIfaceName, wgAddr, iface.DefaultMTU)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed creating wireguard interface instance %s: [%s]", wgIfaceName, err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.udpMuxConn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: e.config.UDPMuxPort})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxPort, err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.udpMuxConnSrflx, err = net.ListenUDP("udp4", &net.UDPAddr{Port: e.config.UDPMuxSrflxPort})
|
||||||
|
if err != nil {
|
||||||
|
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.receiveSignalEvents()
|
||||||
|
e.receiveManagementEvents()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("removed peer %s", p)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) peer.ConnStatus {
|
||||||
|
conn, exists := e.peerConns[peerKey]
|
||||||
|
if exists && conn != nil {
|
||||||
|
return conn.Status()
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Engine) GetPeers() []string {
|
||||||
|
e.syncMsgMux.Lock()
|
||||||
|
defer e.syncMsgMux.Unlock()
|
||||||
|
|
||||||
|
peers := []string{}
|
||||||
|
for s := range e.peerConns {
|
||||||
|
peers = append(peers, s)
|
||||||
|
}
|
||||||
|
return peers
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(),
|
||||||
|
Body: &sProto.Body{
|
||||||
|
Type: sProto.Body_CANDIDATE,
|
||||||
|
Payload: candidate.Marshal(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed signaling candidate to the remote peer %s %s", remoteKey.String(), err)
|
||||||
|
// todo ??
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
t = sProto.Body_OFFER
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := signal.MarshalCredential(myKey, remoteKey, &signal.Credential{
|
||||||
|
UFrag: uFrag,
|
||||||
|
Pwd: pwd,
|
||||||
|
}, t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.Send(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
_ = CtxGetState(e.ctx).Wrap(ErrResetConnection)
|
||||||
|
e.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("stopped receiving updates from Management Service")
|
||||||
|
}()
|
||||||
|
log.Debugf("connecting to Management Service updates stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Engine) updateSTUNs(stuns []*mgmProto.HostConfig) error {
|
||||||
|
if len(stuns) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var newSTUNs []*ice.URL
|
||||||
|
log.Debugf("got STUNs update from Management Service, updating")
|
||||||
|
for _, stun := range stuns {
|
||||||
|
url, err := ice.ParseURL(stun.Uri)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newSTUNs = append(newSTUNs, url)
|
||||||
|
}
|
||||||
|
e.STUNs = newSTUNs
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Engine) updateTURNs(turns []*mgmProto.ProtectedHostConfig) error {
|
||||||
|
if len(turns) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var newTURNs []*ice.URL
|
||||||
|
log.Debugf("got TURNs update from Management Service, updating")
|
||||||
|
for _, turn := range turns {
|
||||||
|
url, err := ice.ParseURL(turn.HostConfig.Uri)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
url.Username = turn.User
|
||||||
|
url.Password = turn.Password
|
||||||
|
newTURNs = append(newTURNs, url)
|
||||||
|
}
|
||||||
|
e.TURNs = newTURNs
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
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()
|
||||||
|
|
||||||
|
conn := e.peerConns[msg.Key]
|
||||||
|
if conn == nil {
|
||||||
|
return fmt.Errorf("wrongly addressed message %s", msg.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// happens if signal is unavailable for a long time.
|
||||||
|
// We want to cancel the operation of the whole client
|
||||||
|
_ = CtxGetState(e.ctx).Wrap(ErrResetConnection)
|
||||||
|
e.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
e.signal.WaitStreamConnected()
|
||||||
|
}
|
||||||
459
client/internal/engine_test.go
Normal file
@@ -0,0 +1,459 @@
|
|||||||
|
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(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||||
|
WgIfaceName: "utun100",
|
||||||
|
WgAddr: "100.64.0.1/24",
|
||||||
|
WgPrivateKey: key,
|
||||||
|
WgPort: 33100,
|
||||||
|
})
|
||||||
|
|
||||||
|
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(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, &EngineConfig{
|
||||||
|
WgIfaceName: "utun100",
|
||||||
|
WgAddr: "100.64.0.1/24",
|
||||||
|
WgPrivateKey: key,
|
||||||
|
WgPort: 33100,
|
||||||
|
})
|
||||||
|
|
||||||
|
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(CtxInitState(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(ctx, cancel, signalClient, mgmtClient, conf), 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
|
||||||
|
}
|
||||||
118
client/internal/login.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/system"
|
||||||
|
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
||||||
|
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Login(ctx context.Context, config *Config, setupKey string) error {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
var mgmTlsEnabled bool
|
||||||
|
if config.ManagementURL.Scheme == "https" {
|
||||||
|
mgmTlsEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
loginOp := func() error {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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("exiting login retry loop due to unrecoverable error: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loginPeer attempts to login to Management Service. If peer wasn't registered, tries the registration flow.
|
||||||
|
func loginPeer(serverPublicKey wgtypes.Key, client *mgm.GrpcClient, setupKey string) (*mgmProto.LoginResponse, error) {
|
||||||
|
loginResp, err := client.Login(serverPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
if s, ok := status.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
||||||
|
log.Debugf("peer registration required")
|
||||||
|
return registerPeer(serverPublicKey, client, setupKey)
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("peer has successfully logged-in to Management Service")
|
||||||
|
|
||||||
|
return loginResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.GrpcClient, setupKey string) (*mgmProto.LoginResponse, error) {
|
||||||
|
validSetupKey, err := uuid.Parse(setupKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("sending peer registration request to Management Service")
|
||||||
|
info := system.GetInfo()
|
||||||
|
loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), info)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed registering peer %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("peer has been successfully registered on Management Service")
|
||||||
|
|
||||||
|
return loginResp, nil
|
||||||
|
}
|
||||||
503
client/internal/peer/conn.go
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
package peer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/wiretrustee/wiretrustee/iface"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn.proxy.Type() == proxy.TypeNoProxy {
|
||||||
|
host, _, _ := net.SplitHostPort(remoteConn.LocalAddr().String())
|
||||||
|
rhost, _, _ := net.SplitHostPort(remoteConn.RemoteAddr().String())
|
||||||
|
// direct Wireguard connection
|
||||||
|
log.Infof("directly connected to peer %s [laddr <-> raddr] [%s:%d <-> %s:%d]", conn.config.Key, host, iface.DefaultWgPort, rhost, iface.DefaultWgPort)
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// useProxy determines whether a direct connection (without a go proxy) is possible
|
||||||
|
// There are 3 cases: one of the peers has a public IP or both peers are in the same private network
|
||||||
|
// Please note, that this check happens when peers were already able to ping each other using ICE layer.
|
||||||
|
func shouldUseProxy(pair *ice.CandidatePair) bool {
|
||||||
|
remoteIP := net.ParseIP(pair.Remote.Address())
|
||||||
|
myIp := net.ParseIP(pair.Local.Address())
|
||||||
|
remoteIsPublic := IsPublicIP(remoteIP)
|
||||||
|
myIsPublic := IsPublicIP(myIp)
|
||||||
|
|
||||||
|
//one of the hosts has a public IP
|
||||||
|
if remoteIsPublic && pair.Remote.Type() == ice.CandidateTypeHost {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if myIsPublic && pair.Local.Type() == ice.CandidateTypeHost {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if pair.Local.Type() == ice.CandidateTypeHost && pair.Remote.Type() == ice.CandidateTypeHost {
|
||||||
|
if !remoteIsPublic && !myIsPublic {
|
||||||
|
//both hosts are in the same private network
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPublicIP indicates whether IP is public or not.
|
||||||
|
func IsPublicIP(ip net.IP) bool {
|
||||||
|
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsPrivate() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
var pair *ice.CandidatePair
|
||||||
|
pair, err := conn.agent.GetSelectedCandidatePair()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
useProxy := shouldUseProxy(pair)
|
||||||
|
var p proxy.Proxy
|
||||||
|
if useProxy {
|
||||||
|
p = proxy.NewWireguardProxy(conn.config.ProxyConfig)
|
||||||
|
} else {
|
||||||
|
p = proxy.NewNoProxy(conn.config.ProxyConfig)
|
||||||
|
}
|
||||||
|
conn.proxy = p
|
||||||
|
err = p.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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
72
client/internal/proxy/dummy.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DummyProxy) Type() Type {
|
||||||
|
return TypeDummy
|
||||||
|
}
|
||||||
52
client/internal/proxy/noproxy.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/wiretrustee/wiretrustee/iface"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NoProxy is used when there is no need for a proxy between ICE and Wireguard.
|
||||||
|
// This is possible in either of these cases:
|
||||||
|
// - peers are in the same local network
|
||||||
|
// - one of the peers has a public static IP (host)
|
||||||
|
// NoProxy will just update remote peer with a remote host and fixed Wireguard port (r.g. 51820).
|
||||||
|
// In order NoProxy to work, Wireguard port has to be fixed for the time being.
|
||||||
|
type NoProxy struct {
|
||||||
|
config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNoProxy(config Config) *NoProxy {
|
||||||
|
return &NoProxy{config: config}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *NoProxy) Close() error {
|
||||||
|
err := p.config.WgInterface.RemovePeer(p.config.RemoteKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start just updates Wireguard peer with the remote IP and default Wireguard port
|
||||||
|
func (p *NoProxy) Start(remoteConn net.Conn) error {
|
||||||
|
|
||||||
|
log.Debugf("using NoProxy while connecting to peer %s", p.config.RemoteKey)
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", remoteConn.RemoteAddr().String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
addr.Port = iface.DefaultWgPort
|
||||||
|
err = p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
|
||||||
|
addr, p.config.PreSharedKey)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *NoProxy) Type() Type {
|
||||||
|
return TypeNoProxy
|
||||||
|
}
|
||||||
34
client/internal/proxy/proxy.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wiretrustee/wiretrustee/iface"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultWgKeepAlive = 25 * time.Second
|
||||||
|
|
||||||
|
type Type string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeNoProxy Type = "NoProxy"
|
||||||
|
TypeWireguard Type = "Wireguard"
|
||||||
|
TypeDummy Type = "Dummy"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
Type() Type
|
||||||
|
}
|
||||||
128
client/internal/proxy/wireguard.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WireguardProxy) Type() Type {
|
||||||
|
return TypeWireguard
|
||||||
|
}
|
||||||
67
client/internal/state.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StatusType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusIdle StatusType = "Idle"
|
||||||
|
|
||||||
|
StatusConnecting StatusType = "Connecting"
|
||||||
|
StatusConnected StatusType = "Connected"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CtxInitState setup context state into the context tree.
|
||||||
|
//
|
||||||
|
// This function should be used to initialize context before
|
||||||
|
// CtxGetState will be executed.
|
||||||
|
func CtxInitState(ctx context.Context) context.Context {
|
||||||
|
return context.WithValue(ctx, stateCtx, &contextState{
|
||||||
|
status: StatusIdle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CtxGetState object to get/update state/errors of process.
|
||||||
|
func CtxGetState(ctx context.Context) *contextState {
|
||||||
|
return ctx.Value(stateCtx).(*contextState)
|
||||||
|
}
|
||||||
|
|
||||||
|
type contextState struct {
|
||||||
|
err error
|
||||||
|
status StatusType
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contextState) Set(update StatusType) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
c.status = update
|
||||||
|
c.err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contextState) Status() (StatusType, error) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
if c.err != nil {
|
||||||
|
return "", c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *contextState) Wrap(err error) error {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
c.err = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type stateKey int
|
||||||
|
|
||||||
|
var stateCtx stateKey
|
||||||
13
client/main.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
17
client/manifest.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<assemblyIdentity
|
||||||
|
version="0.0.0.1"
|
||||||
|
processorArchitecture="*"
|
||||||
|
name="wiretrustee.exe"
|
||||||
|
type="win32"
|
||||||
|
/>
|
||||||
|
<description>Wiretrustee application</description>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
</assembly>
|
||||||
566
client/proto/daemon.pb.go
Normal file
@@ -0,0 +1,566 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.26.0
|
||||||
|
// protoc v3.17.3
|
||||||
|
// source: daemon.proto
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
_ "google.golang.org/protobuf/types/descriptorpb"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// setupKey wiretrustee setup key.
|
||||||
|
SetupKey string `protobuf:"bytes,1,opt,name=setupKey,proto3" json:"setupKey,omitempty"`
|
||||||
|
// presharedKey for wireguard setup.
|
||||||
|
PresharedKey string `protobuf:"bytes,2,opt,name=presharedKey,proto3" json:"presharedKey,omitempty"`
|
||||||
|
// managementUrl to authenticate.
|
||||||
|
ManagementUrl string `protobuf:"bytes,3,opt,name=managementUrl,proto3" json:"managementUrl,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginRequest) Reset() {
|
||||||
|
*x = LoginRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LoginRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LoginRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[0]
|
||||||
|
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 LoginRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LoginRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginRequest) GetSetupKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SetupKey
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginRequest) GetPresharedKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.PresharedKey
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginRequest) GetManagementUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ManagementUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginResponse) Reset() {
|
||||||
|
*x = LoginResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LoginResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LoginResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[1]
|
||||||
|
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 LoginResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LoginResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UpRequest) Reset() {
|
||||||
|
*x = UpRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UpRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UpRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *UpRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[2]
|
||||||
|
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 UpRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*UpRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UpResponse) Reset() {
|
||||||
|
*x = UpResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UpResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UpResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *UpResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[3]
|
||||||
|
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 UpResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*UpResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatusRequest) Reset() {
|
||||||
|
*x = StatusRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatusRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StatusRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StatusRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[4]
|
||||||
|
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 StatusRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StatusRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// status of the server.
|
||||||
|
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatusResponse) Reset() {
|
||||||
|
*x = StatusResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatusResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StatusResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StatusResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[5]
|
||||||
|
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 StatusResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StatusResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatusResponse) GetStatus() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type DownRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DownRequest) Reset() {
|
||||||
|
*x = DownRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DownRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DownRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DownRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[6]
|
||||||
|
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 DownRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DownRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DownResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DownResponse) Reset() {
|
||||||
|
*x = DownResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DownResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DownResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DownResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[7]
|
||||||
|
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 DownResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DownResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_daemon_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_daemon_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x0c, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
|
||||||
|
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||||
|
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x74, 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, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x73, 0x68, 0x61, 0x72, 0x65,
|
||||||
|
0x64, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x73,
|
||||||
|
0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61,
|
||||||
|
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
|
0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x22, 0x0f,
|
||||||
|
0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||||
|
0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a,
|
||||||
|
0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74,
|
||||||
|
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x28, 0x0a, 0x0e, 0x53,
|
||||||
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a,
|
||||||
|
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73,
|
||||||
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70,
|
||||||
|
0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe6, 0x01, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53,
|
||||||
|
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12,
|
||||||
|
0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65,
|
||||||
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c,
|
||||||
|
0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d,
|
||||||
|
0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70,
|
||||||
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
||||||
|
0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a,
|
||||||
|
0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
||||||
|
0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16,
|
||||||
|
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
|
||||||
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e,
|
||||||
|
0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65,
|
||||||
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44,
|
||||||
|
0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a,
|
||||||
|
0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_daemon_proto_rawDescOnce sync.Once
|
||||||
|
file_daemon_proto_rawDescData = file_daemon_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_daemon_proto_rawDescGZIP() []byte {
|
||||||
|
file_daemon_proto_rawDescOnce.Do(func() {
|
||||||
|
file_daemon_proto_rawDescData = protoimpl.X.CompressGZIP(file_daemon_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_daemon_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||||
|
var file_daemon_proto_goTypes = []interface{}{
|
||||||
|
(*LoginRequest)(nil), // 0: daemon.LoginRequest
|
||||||
|
(*LoginResponse)(nil), // 1: daemon.LoginResponse
|
||||||
|
(*UpRequest)(nil), // 2: daemon.UpRequest
|
||||||
|
(*UpResponse)(nil), // 3: daemon.UpResponse
|
||||||
|
(*StatusRequest)(nil), // 4: daemon.StatusRequest
|
||||||
|
(*StatusResponse)(nil), // 5: daemon.StatusResponse
|
||||||
|
(*DownRequest)(nil), // 6: daemon.DownRequest
|
||||||
|
(*DownResponse)(nil), // 7: daemon.DownResponse
|
||||||
|
}
|
||||||
|
var file_daemon_proto_depIdxs = []int32{
|
||||||
|
0, // 0: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
|
||||||
|
2, // 1: daemon.DaemonService.Up:input_type -> daemon.UpRequest
|
||||||
|
4, // 2: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
|
||||||
|
6, // 3: daemon.DaemonService.Down:input_type -> daemon.DownRequest
|
||||||
|
1, // 4: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
||||||
|
3, // 5: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
||||||
|
5, // 6: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
||||||
|
7, // 7: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
||||||
|
4, // [4:8] is the sub-list for method output_type
|
||||||
|
0, // [0:4] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_daemon_proto_init() }
|
||||||
|
func file_daemon_proto_init() {
|
||||||
|
if File_daemon_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_daemon_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*LoginRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_daemon_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*LoginResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_daemon_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*UpRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_daemon_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*UpResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_daemon_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*StatusRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_daemon_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*StatusResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_daemon_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*DownRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_daemon_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*DownResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_daemon_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 8,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_daemon_proto_goTypes,
|
||||||
|
DependencyIndexes: file_daemon_proto_depIdxs,
|
||||||
|
MessageInfos: file_daemon_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_daemon_proto = out.File
|
||||||
|
file_daemon_proto_rawDesc = nil
|
||||||
|
file_daemon_proto_goTypes = nil
|
||||||
|
file_daemon_proto_depIdxs = nil
|
||||||
|
}
|
||||||
49
client/proto/daemon.proto
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "google/protobuf/descriptor.proto";
|
||||||
|
|
||||||
|
option go_package = "/proto";
|
||||||
|
|
||||||
|
package daemon;
|
||||||
|
|
||||||
|
service DaemonService {
|
||||||
|
// Login uses setup key to prepare configuration for the daemon.
|
||||||
|
rpc Login(LoginRequest) returns (LoginResponse) {}
|
||||||
|
|
||||||
|
// Up starts engine work in the daemon.
|
||||||
|
rpc Up(UpRequest) returns (UpResponse) {}
|
||||||
|
|
||||||
|
// Status of the service.
|
||||||
|
rpc Status(StatusRequest) returns (StatusResponse) {}
|
||||||
|
|
||||||
|
// Down engine work in the daemon.
|
||||||
|
rpc Down(DownRequest) returns (DownResponse) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
message LoginRequest {
|
||||||
|
// setupKey wiretrustee setup key.
|
||||||
|
string setupKey = 1;
|
||||||
|
|
||||||
|
// presharedKey for wireguard setup.
|
||||||
|
string presharedKey = 2;
|
||||||
|
|
||||||
|
// managementUrl to authenticate.
|
||||||
|
string managementUrl = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LoginResponse {}
|
||||||
|
|
||||||
|
message UpRequest {}
|
||||||
|
|
||||||
|
message UpResponse {}
|
||||||
|
|
||||||
|
message StatusRequest{}
|
||||||
|
|
||||||
|
message StatusResponse{
|
||||||
|
// status of the server.
|
||||||
|
string status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DownRequest {}
|
||||||
|
|
||||||
|
message DownResponse {}
|
||||||
217
client/proto/daemon_grpc.pb.go
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.32.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion7
|
||||||
|
|
||||||
|
// DaemonServiceClient is the client API for DaemonService service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type DaemonServiceClient interface {
|
||||||
|
// Login uses setup key to prepare configuration for the daemon.
|
||||||
|
Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error)
|
||||||
|
// Up starts engine work in the daemon.
|
||||||
|
Up(ctx context.Context, in *UpRequest, opts ...grpc.CallOption) (*UpResponse, error)
|
||||||
|
// Status of the service.
|
||||||
|
Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
|
||||||
|
// Down engine work in the daemon.
|
||||||
|
Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type daemonServiceClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDaemonServiceClient(cc grpc.ClientConnInterface) DaemonServiceClient {
|
||||||
|
return &daemonServiceClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {
|
||||||
|
out := new(LoginResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/daemon.DaemonService/Login", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) Up(ctx context.Context, in *UpRequest, opts ...grpc.CallOption) (*UpResponse, error) {
|
||||||
|
out := new(UpResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/daemon.DaemonService/Up", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) {
|
||||||
|
out := new(StatusResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/daemon.DaemonService/Status", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error) {
|
||||||
|
out := new(DownResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/daemon.DaemonService/Down", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DaemonServiceServer is the server API for DaemonService service.
|
||||||
|
// All implementations must embed UnimplementedDaemonServiceServer
|
||||||
|
// for forward compatibility
|
||||||
|
type DaemonServiceServer interface {
|
||||||
|
// Login uses setup key to prepare configuration for the daemon.
|
||||||
|
Login(context.Context, *LoginRequest) (*LoginResponse, error)
|
||||||
|
// Up starts engine work in the daemon.
|
||||||
|
Up(context.Context, *UpRequest) (*UpResponse, error)
|
||||||
|
// Status of the service.
|
||||||
|
Status(context.Context, *StatusRequest) (*StatusResponse, error)
|
||||||
|
// Down engine work in the daemon.
|
||||||
|
Down(context.Context, *DownRequest) (*DownResponse, error)
|
||||||
|
mustEmbedUnimplementedDaemonServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedDaemonServiceServer must be embedded to have forward compatible implementations.
|
||||||
|
type UnimplementedDaemonServiceServer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedDaemonServiceServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Login not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) Up(context.Context, *UpRequest) (*UpResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Up not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) Status(context.Context, *StatusRequest) (*StatusResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Status not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) Down(context.Context, *DownRequest) (*DownResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Down not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
||||||
|
|
||||||
|
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to DaemonServiceServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeDaemonServiceServer interface {
|
||||||
|
mustEmbedUnimplementedDaemonServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterDaemonServiceServer(s grpc.ServiceRegistrar, srv DaemonServiceServer) {
|
||||||
|
s.RegisterService(&DaemonService_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _DaemonService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(LoginRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).Login(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/daemon.DaemonService/Login",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).Login(ctx, req.(*LoginRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _DaemonService_Up_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(UpRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).Up(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/daemon.DaemonService/Up",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).Up(ctx, req.(*UpRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _DaemonService_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(StatusRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).Status(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/daemon.DaemonService/Status",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).Status(ctx, req.(*StatusRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _DaemonService_Down_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(DownRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).Down(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/daemon.DaemonService/Down",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).Down(ctx, req.(*DownRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var DaemonService_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "daemon.DaemonService",
|
||||||
|
HandlerType: (*DaemonServiceServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "Login",
|
||||||
|
Handler: _DaemonService_Login_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Up",
|
||||||
|
Handler: _DaemonService_Up_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Status",
|
||||||
|
Handler: _DaemonService_Status_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Down",
|
||||||
|
Handler: _DaemonService_Down_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "daemon.proto",
|
||||||
|
}
|
||||||
4
client/proto/generate.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
|
||||||
|
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
|
||||||
|
protoc -I proto/ proto/daemon.proto --go_out=. --go-grpc_out=.
|
||||||
9
client/resources.rc
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#pragma code_page(65001) // UTF-8
|
||||||
|
|
||||||
|
#define STRINGIZE(x) #x
|
||||||
|
#define EXPAND(x) STRINGIZE(x)
|
||||||
|
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
|
||||||
|
7 ICON ui/wiretrustee.ico
|
||||||
|
wireguard.dll RCDATA wireguard.dll
|
||||||
166
client/server/server.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server for service control.
|
||||||
|
type Server struct {
|
||||||
|
rootCtx context.Context
|
||||||
|
actCancel context.CancelFunc
|
||||||
|
|
||||||
|
managementURL string
|
||||||
|
configPath string
|
||||||
|
stopCh chan int
|
||||||
|
cleanupCh chan<- struct{}
|
||||||
|
|
||||||
|
mutex sync.Mutex
|
||||||
|
config *internal.Config
|
||||||
|
proto.UnimplementedDaemonServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
// New server instance constructor.
|
||||||
|
func New(
|
||||||
|
ctx context.Context, managementURL, configPath string,
|
||||||
|
stopCh chan int, cleanupCh chan<- struct{},
|
||||||
|
) *Server {
|
||||||
|
return &Server{
|
||||||
|
rootCtx: ctx,
|
||||||
|
managementURL: managementURL,
|
||||||
|
configPath: configPath,
|
||||||
|
stopCh: stopCh,
|
||||||
|
cleanupCh: cleanupCh,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start() error {
|
||||||
|
state := internal.CtxGetState(s.rootCtx)
|
||||||
|
|
||||||
|
// if current state contains any error, return it
|
||||||
|
// in all other cases we can continue execution only if status is idle and up command was
|
||||||
|
// not in the progress or already successfully estabilished connection.
|
||||||
|
status, err := state.Status()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status != internal.StatusIdle {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(s.rootCtx)
|
||||||
|
s.actCancel = cancel
|
||||||
|
|
||||||
|
// if configuration exists, we just start connections.
|
||||||
|
config, err := internal.ReadConfig(s.managementURL, s.configPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("no config file, skip connection stage: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := internal.RunClient(ctx, config, s.stopCh, s.cleanupCh); err != nil {
|
||||||
|
log.Errorf("init connections: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login uses setup key to prepare configuration for the daemon.
|
||||||
|
func (s *Server) Login(_ context.Context, msg *proto.LoginRequest) (*proto.LoginResponse, error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
managementURL := s.managementURL
|
||||||
|
if msg.ManagementUrl != "" {
|
||||||
|
managementURL = msg.ManagementUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := internal.GetConfig(managementURL, s.configPath, msg.PresharedKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.config = config
|
||||||
|
|
||||||
|
// login operation uses backoff scheme to connect to management API
|
||||||
|
// we don't wait for result and return response immediately.
|
||||||
|
if err := internal.Login(s.rootCtx, s.config, msg.SetupKey); err != nil {
|
||||||
|
log.Errorf("failed login: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &proto.LoginResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Up starts engine work in the daemon.
|
||||||
|
func (s *Server) Up(_ context.Context, msg *proto.UpRequest) (*proto.UpResponse, error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
state := internal.CtxGetState(s.rootCtx)
|
||||||
|
|
||||||
|
// if current state contains any error, return it
|
||||||
|
// in all other cases we can continue execution only if status is idle and up command was
|
||||||
|
// not in the progress or already successfully estabilished connection.
|
||||||
|
status, err := state.Status()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if status != internal.StatusIdle {
|
||||||
|
return nil, fmt.Errorf("up already in progress: current status %s", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// it should be nill here, but .
|
||||||
|
if s.actCancel != nil {
|
||||||
|
s.actCancel()
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(s.rootCtx)
|
||||||
|
s.actCancel = cancel
|
||||||
|
|
||||||
|
if s.config == nil {
|
||||||
|
return nil, fmt.Errorf("config is not defined, please call login command first")
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := internal.RunClient(ctx, s.config, s.stopCh, s.cleanupCh); err != nil {
|
||||||
|
log.Errorf("run client connection: %v", state.Wrap(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return &proto.UpResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Down dengine work in the daemon.
|
||||||
|
func (s *Server) Down(ctx context.Context, msg *proto.DownRequest) (*proto.DownResponse, error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.actCancel == nil {
|
||||||
|
return nil, fmt.Errorf("service is not up")
|
||||||
|
}
|
||||||
|
s.actCancel()
|
||||||
|
|
||||||
|
return &proto.DownResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status starts engine work in the daemon.
|
||||||
|
func (s *Server) Status(ctx context.Context, msg *proto.StatusRequest) (*proto.StatusResponse, error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
status, err := internal.CtxGetState(s.rootCtx).Status()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &proto.StatusResponse{Status: string(status)}, nil
|
||||||
|
}
|
||||||
23
client/system/info.go
Normal file
@@ -0,0 +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
|
||||||
|
WiretrusteeVersion string
|
||||||
|
}
|
||||||
|
|
||||||
|
func WiretrusteeVersion() string {
|
||||||
|
return version
|
||||||
|
}
|
||||||
40
client/system/info_darwin.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetInfo() *Info {
|
||||||
|
out := _getInfo()
|
||||||
|
for strings.Contains(out, "broken pipe") {
|
||||||
|
out = _getInfo()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
osStr := strings.Replace(out, "\n", "", -1)
|
||||||
|
osStr = strings.Replace(osStr, "\r\n", "", -1)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func _getInfo() string {
|
||||||
|
cmd := exec.Command("uname", "-srm")
|
||||||
|
cmd.Stdin = strings.NewReader("some input")
|
||||||
|
var out bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("getInfo:", err)
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
40
client/system/info_freebsd.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetInfo() *Info {
|
||||||
|
out := _getInfo()
|
||||||
|
for strings.Contains(out, "broken pipe") {
|
||||||
|
out = _getInfo()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
osStr := strings.Replace(out, "\n", "", -1)
|
||||||
|
osStr = strings.Replace(osStr, "\r\n", "", -1)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func _getInfo() string {
|
||||||
|
cmd := exec.Command("uname", "-sri")
|
||||||
|
cmd.Stdin = strings.NewReader("some input")
|
||||||
|
var out bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("getInfo:", err)
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
78
client/system/info_linux.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetInfo() *Info {
|
||||||
|
info := _getInfo()
|
||||||
|
for strings.Contains(info, "broken pipe") {
|
||||||
|
info = _getInfo()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseInfo := _getReleaseInfo()
|
||||||
|
for strings.Contains(info, "broken pipe") {
|
||||||
|
releaseInfo = _getReleaseInfo()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
osRelease := strings.Split(releaseInfo, "\n")
|
||||||
|
var osName string
|
||||||
|
var osVer string
|
||||||
|
for _, s := range osRelease {
|
||||||
|
if strings.HasPrefix(s, "NAME=") {
|
||||||
|
osName = strings.Split(s, "=")[1]
|
||||||
|
osName = strings.ReplaceAll(osName, "\"", "")
|
||||||
|
} else if strings.HasPrefix(s, "VERSION_ID=") {
|
||||||
|
osVer = strings.Split(s, "=")[1]
|
||||||
|
osVer = strings.ReplaceAll(osVer, "\"", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
osStr := strings.Replace(info, "\n", "", -1)
|
||||||
|
osStr = strings.Replace(osStr, "\r\n", "", -1)
|
||||||
|
osInfo := strings.Split(osStr, " ")
|
||||||
|
if osName == "" {
|
||||||
|
osName = osInfo[3]
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func _getInfo() string {
|
||||||
|
cmd := exec.Command("uname", "-srio")
|
||||||
|
cmd.Stdin = strings.NewReader("some")
|
||||||
|
var out bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("getInfo:", err)
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func _getReleaseInfo() string {
|
||||||
|
cmd := exec.Command("cat", "/etc/os-release")
|
||||||
|
cmd.Stdin = strings.NewReader("some")
|
||||||
|
var out bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("getReleaseInfo:", err)
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
37
client/system/info_windows.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetInfo() *Info {
|
||||||
|
cmd := exec.Command("cmd", "ver")
|
||||||
|
cmd.Stdin = strings.NewReader("some")
|
||||||
|
var out bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
osStr := strings.Replace(out.String(), "\n", "", -1)
|
||||||
|
osStr = strings.Replace(osStr, "\r\n", "", -1)
|
||||||
|
tmp1 := strings.Index(osStr, "[Version")
|
||||||
|
tmp2 := strings.Index(osStr, "]")
|
||||||
|
var ver string
|
||||||
|
if tmp1 == -1 || tmp2 == -1 {
|
||||||
|
ver = "unknown"
|
||||||
|
} else {
|
||||||
|
ver = osStr[tmp1+9 : tmp2]
|
||||||
|
}
|
||||||
|
gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
||||||
|
gio.Hostname, _ = os.Hostname()
|
||||||
|
gio.WiretrusteeVersion = WiretrusteeVersion()
|
||||||
|
|
||||||
|
return gio
|
||||||
|
}
|
||||||
37
client/testdata/management.json
vendored
Normal file
@@ -0,0 +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>"
|
||||||
|
}
|
||||||
|
}
|
||||||
38
client/testdata/store.json
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"Accounts": {
|
||||||
|
"bf1c8084-ba50-4ce7-9439-34653001fc3b": {
|
||||||
|
"Id": "bf1c8084-ba50-4ce7-9439-34653001fc3b",
|
||||||
|
"SetupKeys": {
|
||||||
|
"A2C8E62B-38F5-4553-B31E-DD66C696CEBB": {
|
||||||
|
"Key": "A2C8E62B-38F5-4553-B31E-DD66C696CEBB",
|
||||||
|
"Name": "Default key",
|
||||||
|
"Type": "reusable",
|
||||||
|
"CreatedAt": "2021-08-19T20:46:20.005936822+02:00",
|
||||||
|
"ExpiresAt": "2321-09-18T20:46:20.005936822+02:00",
|
||||||
|
"Revoked": false,
|
||||||
|
"UsedTimes": 0
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Network": {
|
||||||
|
"Id": "af1c8024-ha40-4ce2-9418-34653101fc3c",
|
||||||
|
"Net": {
|
||||||
|
"IP": "100.64.0.0",
|
||||||
|
"Mask": "/8AAAA=="
|
||||||
|
},
|
||||||
|
"Dns": null
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
client/ui/banner.bmp
Normal file
|
After Width: | Height: | Size: 26 KiB |
96
client/ui/client_ui.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getlantern/systray"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/skratchdot/open-golang/open"
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/proto"
|
||||||
|
"github.com/wiretrustee/wiretrustee/client/ui/config"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
systray.Run(onReady, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implementation for SSO Logins
|
||||||
|
func onReady() {
|
||||||
|
wtIcon, err := ioutil.ReadFile("wiretrustee.ico")
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
}
|
||||||
|
if wtIcon != nil {
|
||||||
|
systray.SetTemplateIcon(wtIcon, wtIcon)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
up := systray.AddMenuItem("Up", "Up")
|
||||||
|
down := systray.AddMenuItem("Down", "Down")
|
||||||
|
|
||||||
|
mUrl := systray.AddMenuItem("Open UI", "wiretrustee website")
|
||||||
|
systray.AddSeparator()
|
||||||
|
|
||||||
|
mQuitOrig := systray.AddMenuItem("Quit", "Quit the whole app")
|
||||||
|
go func() {
|
||||||
|
<-mQuitOrig.ClickedCh
|
||||||
|
fmt.Println("Requesting quit")
|
||||||
|
systray.Quit()
|
||||||
|
fmt.Println("Finished quitting")
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-mUrl.ClickedCh:
|
||||||
|
open.Run("https://app.wiretrustee.com")
|
||||||
|
case <-up.ClickedCh:
|
||||||
|
upCmdExec()
|
||||||
|
case <-down.ClickedCh:
|
||||||
|
fmt.Println("Clicked down")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUp() {
|
||||||
|
// This is where
|
||||||
|
}
|
||||||
|
|
||||||
|
func upCmdExec() {
|
||||||
|
log.Println("executing up command..")
|
||||||
|
cfg := config.Config()
|
||||||
|
|
||||||
|
ctx := internal.CtxInitState(context.Background())
|
||||||
|
|
||||||
|
conn, err := grpc.DialContext(ctx, cfg.DaemonAddr(),
|
||||||
|
grpc.WithTimeout(5*time.Second),
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
grpc.WithBlock())
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to connect to service CLI interface; %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
daemonClient := proto.NewDaemonServiceClient(conn)
|
||||||
|
|
||||||
|
status, err := daemonClient.Status(ctx, &proto.StatusRequest{})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("get status: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.Status != string(internal.StatusIdle) {
|
||||||
|
log.Warnf("already connected")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = daemonClient.Up(ctx, &proto.UpRequest{})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to start up client; %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
41
client/ui/config/config.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientConfig struct {
|
||||||
|
configPath string
|
||||||
|
logFile string
|
||||||
|
daemonAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are creating this package to extract utility functions from the cmd package
|
||||||
|
// reading and parsing the configurations for the client should be done here
|
||||||
|
func Config() *ClientConfig {
|
||||||
|
defaultConfigPath := "/etc/wiretrustee/config.json"
|
||||||
|
defaultLogFile := "/var/log/wiretrustee/client.log"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
defaultConfigPath = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "config.json"
|
||||||
|
defaultLogFile = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "client.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultDaemonAddr := "unix:///var/run/wiretrustee.sock"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
defaultDaemonAddr = "tcp://127.0.0.1:41731"
|
||||||
|
}
|
||||||
|
return &ClientConfig{
|
||||||
|
configPath: defaultConfigPath,
|
||||||
|
logFile: defaultLogFile,
|
||||||
|
daemonAddr: defaultDaemonAddr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConfig) DaemonAddr() string {
|
||||||
|
return c.daemonAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConfig) LogFile() string {
|
||||||
|
return c.logFile
|
||||||
|
}
|
||||||
BIN
client/ui/wiretrustee.ico
Normal file
|
After Width: | Height: | Size: 99 KiB |
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
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/wiretrustee/wiretrustee/connection"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
key string
|
|
||||||
allowedIPs string
|
|
||||||
|
|
||||||
addPeerCmd = &cobra.Command{
|
|
||||||
Use: "add-peer",
|
|
||||||
Short: "add remote peer",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
InitLog(logLevel)
|
|
||||||
|
|
||||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
|
||||||
log.Error("config doesn't exist, please run 'wiretrustee init' first")
|
|
||||||
os.Exit(ExitSetupFailed)
|
|
||||||
}
|
|
||||||
|
|
||||||
config, _ := Read(configPath)
|
|
||||||
config.Peers = append(config.Peers, connection.Peer{
|
|
||||||
WgPubKey: key,
|
|
||||||
WgAllowedIps: allowedIPs,
|
|
||||||
})
|
|
||||||
|
|
||||||
err := config.Write(configPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed writing config to %s: %s", config, err.Error())
|
|
||||||
os.Exit(ExitSetupFailed)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
addPeerCmd.PersistentFlags().StringVar(&key, "key", "", "Wireguard public key of the remote peer")
|
|
||||||
addPeerCmd.PersistentFlags().StringVar(&allowedIPs, "allowedIPs", "", "Wireguard Allowed IPs for the remote peer, e.g 10.30.30.2/32")
|
|
||||||
addPeerCmd.MarkPersistentFlagRequired("key")
|
|
||||||
addPeerCmd.MarkPersistentFlagRequired("allowedIPs")
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/pion/ice/v2"
|
|
||||||
"github.com/wiretrustee/wiretrustee/connection"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
// Wireguard private key of local peer
|
|
||||||
PrivateKey string
|
|
||||||
Peers []connection.Peer
|
|
||||||
StunTurnURLs []*ice.URL
|
|
||||||
// host:port of the signal server
|
|
||||||
SignalAddr string
|
|
||||||
WgAddr string
|
|
||||||
WgIface string
|
|
||||||
}
|
|
||||||
|
|
||||||
//Write writes configPath to a file
|
|
||||||
func (cfg *Config) Write(path string) error {
|
|
||||||
bs, err := json.Marshal(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(path, bs, 0600)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//Read reads configPath from a file
|
|
||||||
func Read(path string) (*Config, error) {
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
bs, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfg Config
|
|
||||||
err = json.Unmarshal(bs, &cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cfg, nil
|
|
||||||
}
|
|
||||||
125
cmd/init.go
@@ -1,125 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/pion/ice/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
wgKey string
|
|
||||||
wgInterface string
|
|
||||||
wgLocalAddr string
|
|
||||||
signalAddr string
|
|
||||||
stunURLs string
|
|
||||||
turnURLs string
|
|
||||||
|
|
||||||
initCmd = &cobra.Command{
|
|
||||||
Use: "init",
|
|
||||||
Short: "init wiretrustee",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
InitLog(logLevel)
|
|
||||||
|
|
||||||
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
|
|
||||||
log.Warnf("config already exists under path %s", configPath)
|
|
||||||
os.Exit(ExitSetupFailed)
|
|
||||||
}
|
|
||||||
|
|
||||||
if wgKey == "" {
|
|
||||||
wgKey = generateKey()
|
|
||||||
log.Warnf("there was no Wireguard private key specified, a new Wireguard key has been generated")
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedKey, err := wgtypes.ParseKey(wgKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("invalid Wireguard private key %s", wgKey)
|
|
||||||
os.Exit(ExitSetupFailed)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("my public Wireguard key is %s", parsedKey.PublicKey().String())
|
|
||||||
|
|
||||||
var stunTurnURLs []*ice.URL
|
|
||||||
stuns := strings.Split(stunURLs, ",")
|
|
||||||
for _, url := range stuns {
|
|
||||||
|
|
||||||
parsedURL, err := ice.ParseURL(url)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed parsing STUN URL %s: %s", url, err.Error())
|
|
||||||
os.Exit(ExitSetupFailed)
|
|
||||||
}
|
|
||||||
stunTurnURLs = append(stunTurnURLs, parsedURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
turns := strings.Split(turnURLs, ",")
|
|
||||||
for _, url := range turns {
|
|
||||||
|
|
||||||
var urlToParse string
|
|
||||||
var user string
|
|
||||||
var pwd string
|
|
||||||
//extract user:password from user:password@proto:host:port
|
|
||||||
urlSplit := strings.Split(url, "@")
|
|
||||||
if len(urlSplit) == 2 {
|
|
||||||
urlToParse = urlSplit[1]
|
|
||||||
credential := strings.Split(urlSplit[0], ":")
|
|
||||||
user = credential[0]
|
|
||||||
pwd = credential[1]
|
|
||||||
} else {
|
|
||||||
urlToParse = url
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedURL, err := ice.ParseURL(urlToParse)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed parsing TURN URL %s: %s", url, err.Error())
|
|
||||||
os.Exit(ExitSetupFailed)
|
|
||||||
}
|
|
||||||
parsedURL.Username = user
|
|
||||||
parsedURL.Password = pwd
|
|
||||||
stunTurnURLs = append(stunTurnURLs, parsedURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &Config{
|
|
||||||
PrivateKey: wgKey,
|
|
||||||
Peers: nil,
|
|
||||||
StunTurnURLs: stunTurnURLs,
|
|
||||||
SignalAddr: signalAddr,
|
|
||||||
WgAddr: wgLocalAddr,
|
|
||||||
WgIface: wgInterface,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = config.Write(configPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed writing config to %s: %s", config, err.Error())
|
|
||||||
os.Exit(ExitSetupFailed)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("a new config has been generated and written to %s", configPath)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
initCmd.PersistentFlags().StringVar(&wgKey, "wgKey", "", "Wireguard private key, if not specified a new one will be generated")
|
|
||||||
initCmd.PersistentFlags().StringVar(&wgInterface, "wgInterface", "wiretrustee0", "Wireguard interface name, e.g. wiretreustee0 or wg0")
|
|
||||||
initCmd.PersistentFlags().StringVar(&wgLocalAddr, "wgLocalAddr", "", "Wireguard local address, e.g. 10.30.30.1/24")
|
|
||||||
initCmd.PersistentFlags().StringVar(&signalAddr, "signalAddr", "", "Signal server address, e.g. signal.wiretrustee.com:10000")
|
|
||||||
initCmd.PersistentFlags().StringVar(&stunURLs, "stunURLs", "", "Comma separated STUN server URLs: protocol:host:port, e.g. stun:stun.l.google.com:19302,stun:stun1.l.google.com:19302")
|
|
||||||
//todo user:password@protocol:host:port not the best way to pass TURN credentials, do it according to https://tools.ietf.org/html/rfc7065 E.g. use oauth
|
|
||||||
initCmd.PersistentFlags().StringVar(&turnURLs, "turnURLs", "", "Comma separated TURN server URLs: user:password@protocol:host:port, e.g. user:password@turn:stun.wiretrustee.com:3468")
|
|
||||||
//initCmd.MarkPersistentFlagRequired("configPath")
|
|
||||||
initCmd.MarkPersistentFlagRequired("wgLocalAddr")
|
|
||||||
initCmd.MarkPersistentFlagRequired("signalAddr")
|
|
||||||
initCmd.MarkPersistentFlagRequired("stunURLs")
|
|
||||||
initCmd.MarkPersistentFlagRequired("turnURLs")
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateKey generates a new Wireguard private key
|
|
||||||
func generateKey() string {
|
|
||||||
key, err := wgtypes.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return key.String()
|
|
||||||
}
|
|
||||||
56
cmd/root.go
@@ -1,56 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ExitSetupFailed = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
configPath string
|
|
||||||
logLevel string
|
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
|
||||||
Use: "wiretrustee",
|
|
||||||
Short: "",
|
|
||||||
Long: "",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Execute executes the root command.
|
|
||||||
func Execute() error {
|
|
||||||
return rootCmd.Execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.PersistentFlags().StringVar(&configPath, "config", "/etc/wiretrustee/config.json", "Wiretrustee config file location to write new config to")
|
|
||||||
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
|
|
||||||
rootCmd.AddCommand(initCmd)
|
|
||||||
rootCmd.AddCommand(addPeerCmd)
|
|
||||||
rootCmd.AddCommand(upCmd)
|
|
||||||
rootCmd.AddCommand(signalCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetupCloseHandler() {
|
|
||||||
c := make(chan os.Signal)
|
|
||||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
|
||||||
<-c
|
|
||||||
fmt.Println("\r- Ctrl+C pressed in Terminal")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitLog(logLevel string) {
|
|
||||||
level, err := log.ParseLevel(logLevel)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("efailed parsing log-level %s: %s", logLevel, err)
|
|
||||||
os.Exit(ExitSetupFailed)
|
|
||||||
}
|
|
||||||
log.SetLevel(level)
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
sig "github.com/wiretrustee/wiretrustee/signal"
|
|
||||||
sProto "github.com/wiretrustee/wiretrustee/signal/proto"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
port int
|
|
||||||
|
|
||||||
signalCmd = &cobra.Command{
|
|
||||||
Use: "signal",
|
|
||||||
Short: "start Wiretrustee Signal Server",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to listen: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to listen: %v", err)
|
|
||||||
}
|
|
||||||
var opts []grpc.ServerOption
|
|
||||||
grpcServer := grpc.NewServer(opts...)
|
|
||||||
sProto.RegisterSignalExchangeServer(grpcServer, sig.NewServer())
|
|
||||||
log.Printf("started server: localhost:%v", port)
|
|
||||||
if err := grpcServer.Serve(lis); err != nil {
|
|
||||||
log.Fatalf("failed to serve: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
SetupCloseHandler()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
signalCmd.PersistentFlags().IntVar(&port, "port", 10000, "Server port to listen on (e.g. 10000)")
|
|
||||||
}
|
|
||||||
53
cmd/up.go
@@ -1,53 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/wiretrustee/wiretrustee/connection"
|
|
||||||
sig "github.com/wiretrustee/wiretrustee/signal"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func toByte32(key wgtypes.Key) *[32]byte {
|
|
||||||
return (*[32]byte)(&key)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
upCmd = &cobra.Command{
|
|
||||||
Use: "up",
|
|
||||||
Short: "start wiretrustee",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
InitLog(logLevel)
|
|
||||||
|
|
||||||
config, _ := Read(configPath)
|
|
||||||
|
|
||||||
myKey, err := wgtypes.ParseKey(config.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
|
||||||
os.Exit(ExitSetupFailed)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
signalClient, err := sig.NewClient(config.SignalAddr, myKey, ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("error while connecting to the Signal Exchange Service %s: %s", config.SignalAddr, err)
|
|
||||||
os.Exit(ExitSetupFailed)
|
|
||||||
}
|
|
||||||
//todo proper close handling
|
|
||||||
defer func() { signalClient.Close() }()
|
|
||||||
|
|
||||||
engine := connection.NewEngine(signalClient, config.StunTurnURLs, config.WgIface, config.WgAddr)
|
|
||||||
|
|
||||||
err = engine.Start(myKey, config.Peers)
|
|
||||||
|
|
||||||
//signalClient.WaitConnected()
|
|
||||||
|
|
||||||
SetupCloseHandler()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package connection
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
package connection
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/pion/ice/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DefaultWgKeepAlive = 20 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
type IceCredentials struct {
|
|
||||||
uFrag string
|
|
||||||
pwd string
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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{
|
|
||||||
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4},
|
|
||||||
Urls: conn.Config.StunTurnURLS,
|
|
||||||
})
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
err = conn.wgProxy.Start(remoteConn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("opened connection to peer %s", 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())
|
|
||||||
}
|
|
||||||
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
|
|
||||||
select {
|
|
||||||
case <-conn.closeCond.C:
|
|
||||||
return fmt.Errorf("connection to peer %s has been closed", conn.Config.RemoteWgKey.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
}
|
|
||||||
|
|
||||||
err = conn.signalAnswer(uFrag, pwd)
|
|
||||||
if err != nil {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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("closed to peer %s via selected candidate pair %s", conn.Config.RemoteWgKey.String(), pair)
|
|
||||||
} else if state == ice.ConnectionStateDisconnected || state == ice.ConnectionStateFailed {
|
|
||||||
// todo do we really wanna have a connection restart within connection itself? Think of moving it outside
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
package connection
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
|
||||||
"github.com/pion/ice/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/wiretrustee/wiretrustee/iface"
|
|
||||||
"github.com/wiretrustee/wiretrustee/signal"
|
|
||||||
sProto "github.com/wiretrustee/wiretrustee/signal/proto"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Engine struct {
|
|
||||||
// a list of STUN and TURN servers
|
|
||||||
stunsTurns []*ice.URL
|
|
||||||
// signal server client
|
|
||||||
signal *signal.Client
|
|
||||||
// peer agents indexed by local public key of the remote peers
|
|
||||||
conns map[string]*Connection
|
|
||||||
// Wireguard interface
|
|
||||||
wgIface string
|
|
||||||
// Wireguard local address
|
|
||||||
wgIp string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Peer struct {
|
|
||||||
WgPubKey string
|
|
||||||
WgAllowedIps string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEngine(signal *signal.Client, stunsTurns []*ice.URL, wgIface string, wgAddr string) *Engine {
|
|
||||||
return &Engine{
|
|
||||||
stunsTurns: stunsTurns,
|
|
||||||
signal: signal,
|
|
||||||
wgIface: wgIface,
|
|
||||||
wgIp: wgAddr,
|
|
||||||
conns: map[string]*Connection{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Engine) Start(myKey wgtypes.Key, peers []Peer) error {
|
|
||||||
|
|
||||||
err := iface.Create(e.wgIface, e.wgIp)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("error while creating interface %s: [%s]", e.wgIface, err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = iface.Configure(e.wgIface, myKey.String())
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("error while configuring Wireguard interface [%s]: %s", e.wgIface, err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
wgPort, err := iface.GetListenPort(e.wgIface)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("error while getting Wireguard interface port [%s]: %s", e.wgIface, err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.receiveSignal()
|
|
||||||
|
|
||||||
// initialize peer agents
|
|
||||||
for _, peer := range peers {
|
|
||||||
|
|
||||||
peer := peer
|
|
||||||
go func() {
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
operation := func() error {
|
|
||||||
_, err := e.openPeerConnection(*wgPort, myKey, peer)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("retrying connection because of error: ", err.Error())
|
|
||||||
e.conns[peer.WgPubKey] = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
backOff.Reset()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = backoff.Retry(operation, backOff)
|
|
||||||
if err != nil {
|
|
||||||
// should actually never happen
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Engine) openPeerConnection(wgPort int, myKey wgtypes.Key, peer Peer) (*Connection, error) {
|
|
||||||
|
|
||||||
remoteKey, _ := wgtypes.ParseKey(peer.WgPubKey)
|
|
||||||
connConfig := &ConnConfig{
|
|
||||||
WgListenAddr: fmt.Sprintf("127.0.0.1:%d", wgPort),
|
|
||||||
WgPeerIp: e.wgIp,
|
|
||||||
WgIface: e.wgIface,
|
|
||||||
WgAllowedIPs: peer.WgAllowedIps,
|
|
||||||
WgKey: myKey,
|
|
||||||
RemoteWgKey: remoteKey,
|
|
||||||
StunTurnURLS: e.stunsTurns,
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
// blocks until the connection is open (or timeout)
|
|
||||||
err := conn.Open(60 * time.Second)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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(),
|
|
||||||
Body: &sProto.Body{
|
|
||||||
Type: sProto.Body_CANDIDATE,
|
|
||||||
Payload: candidate.Marshal(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed signaling candidate to the remote peer %s %s", remoteKey.String(), err)
|
|
||||||
//todo ??
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
} else {
|
|
||||||
t = sProto.Body_OFFER
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := signal.MarshalCredential(myKey, remoteKey, &signal.Credential{
|
|
||||||
UFrag: uFrag,
|
|
||||||
Pwd: pwd}, t)
|
|
||||||
|
|
||||||
err = s.Send(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Engine) receiveSignal() {
|
|
||||||
// connect to a stream of messages coming from the signal server
|
|
||||||
e.signal.Receive(func(msg *sProto.Message) error {
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
err = conn.OnOffer(IceCredentials{
|
|
||||||
uFrag: remoteCred.UFrag,
|
|
||||||
pwd: remoteCred.Pwd,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
e.signal.WaitConnected()
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
package connection
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/pion/ice/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/wiretrustee/wiretrustee/iface"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WgProxy struct {
|
|
||||||
iface string
|
|
||||||
remoteKey string
|
|
||||||
allowedIps string
|
|
||||||
wgAddr string
|
|
||||||
close chan struct{}
|
|
||||||
wgConn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
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{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *WgProxy) Close() error {
|
|
||||||
|
|
||||||
close(p.close)
|
|
||||||
if c := p.wgConn; c != nil {
|
|
||||||
err := p.wgConn.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = remoteConn.Write(buf[:n])
|
|
||||||
if err != nil {
|
|
||||||
//log.Warnln("failed writing to remote peer: ", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = p.wgConn.Write(buf[:n])
|
|
||||||
if err != nil {
|
|
||||||
//log.Errorf("failed writing to local Wireguard instance %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 |
BIN
docs/media/peers.gif
Normal file
|
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.
|
||||||
104
docs/self-hosting.md
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
### 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. It also offers an always free plan that should be ok for most users as its limits are high enough for most teams.
|
||||||
|
|
||||||
|
If you would like to learn more about the architecture please refer to the [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 Unix OS.
|
||||||
|
- Docker Compose installed (see [Install Docker Compose](https://docs.docker.com/compose/install/)).
|
||||||
|
- Domain name pointing to the public IP address of your server.
|
||||||
|
- Wiretrustee Open ports ```443, 33071, 33073, 10000``` (Dashboard, Management HTTP API, Management gRpc API, Signal gRpc) on your server.
|
||||||
|
- Coturn is used for relay using the STUN/TURN protocols. It requires a listening port, ```UDP 3478```, and range of ports,```UDP 49152-65535```, for dynamic relay connections.
|
||||||
|
- 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 [turnserver.conf.tmpl](../infrastructure_files/turnserver.conf.tmpl),[docker-compose.yml.tmpl](../infrastructure_files/docker-compose.yml.tmpl) and [management.json.tmpl](../infrastructure_files/management.json.tmpl) files.
|
||||||
|
|
||||||
|
The [setup.env](../infrastructure_files/setup.env) file contains the following properties that have to be filled:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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=""
|
||||||
|
```
|
||||||
|
> Other options are available, but they are automatically updated.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
10. Once the server is running, you can access the dashboard by https://$WIRETRUSTEE_DOMAIN
|
||||||
|
11. Adding a peer will require you to enter the management URL by following the steps in the page https://$WIRETRUSTEE_DOMAIN/add-peer and in the 3rd step:
|
||||||
|
```shell
|
||||||
|
sudo wiretrustee up --setup-key <PASTE-SETUP-KEY> --management-url https://$WIRETRUSTEE_DOMAIN:33073
|
||||||
|
```
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package signal
|
package encryption
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@@ -7,31 +7,29 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// As set of tools to encrypt/decrypt messages being sent through the Signal Exchange Service.
|
// A set of tools to encrypt/decrypt messages being sent through the Signal Exchange Service or Management Service
|
||||||
// We want to make sure that the Connection Candidates and other irrelevant (to the Signal Exchange)
|
|
||||||
// information can't be read anywhere else but the Peer the message is being sent to.
|
|
||||||
// These tools use Golang crypto package (Curve25519, XSalsa20 and Poly1305 to encrypt and authenticate)
|
// These tools use Golang crypto package (Curve25519, XSalsa20 and Poly1305 to encrypt and authenticate)
|
||||||
// Wireguard keys are used for encryption
|
// Wireguard keys are used for encryption
|
||||||
|
|
||||||
// Encrypts a message using local Wireguard private key and remote peer's public key.
|
// Encrypt encrypts a message using local Wireguard private key and remote peer's public key.
|
||||||
func Encrypt(msg []byte, peersPublicKey wgtypes.Key, privateKey wgtypes.Key) ([]byte, error) {
|
func Encrypt(msg []byte, peerPublicKey wgtypes.Key, privateKey wgtypes.Key) ([]byte, error) {
|
||||||
nonce, err := genNonce()
|
nonce, err := genNonce()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return box.Seal(nonce[:], msg, nonce, toByte32(peersPublicKey), toByte32(privateKey)), nil
|
return box.Seal(nonce[:], msg, nonce, toByte32(peerPublicKey), toByte32(privateKey)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypts a message that has been encrypted by the remote peer using Wireguard private key and remote peer's public key.
|
// Decrypt decrypts a message that has been encrypted by the remote peer using Wireguard private key and remote peer's public key.
|
||||||
func Decrypt(encryptedMsg []byte, peersPublicKey wgtypes.Key, privateKey wgtypes.Key) ([]byte, error) {
|
func Decrypt(encryptedMsg []byte, peerPublicKey wgtypes.Key, privateKey wgtypes.Key) ([]byte, error) {
|
||||||
nonce, err := genNonce()
|
nonce, err := genNonce()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
copy(nonce[:], encryptedMsg[:24])
|
copy(nonce[:], encryptedMsg[:24])
|
||||||
opened, ok := box.Open(nil, encryptedMsg[24:], nonce, toByte32(peersPublicKey), toByte32(privateKey))
|
opened, ok := box.Open(nil, encryptedMsg[24:], nonce, toByte32(peerPublicKey), toByte32(privateKey))
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("failed to decrypt message from peer %s", peersPublicKey.String())
|
return nil, fmt.Errorf("failed to decrypt message from peer %s", peerPublicKey.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return opened, nil
|
return opened, nil
|
||||||
13
encryption/encryption_suite_test.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package encryption_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestManagement(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Management Service Suite")
|
||||||
|
}
|
||||||
60
encryption/encryption_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package encryption_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/wiretrustee/wiretrustee/encryption"
|
||||||
|
"github.com/wiretrustee/wiretrustee/encryption/testprotos"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ()
|
||||||
|
|
||||||
|
var _ = Describe("Encryption", func() {
|
||||||
|
|
||||||
|
var (
|
||||||
|
encryptionKey wgtypes.Key
|
||||||
|
decryptionKey wgtypes.Key
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
var err error
|
||||||
|
encryptionKey, err = wgtypes.GenerateKey()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
decryptionKey, err = wgtypes.GenerateKey()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("decrypting a plain message", func() {
|
||||||
|
Context("when it was encrypted with Wireguard keys", func() {
|
||||||
|
Specify("should be successful", func() {
|
||||||
|
msg := "message"
|
||||||
|
encryptedMsg, err := encryption.Encrypt([]byte(msg), decryptionKey.PublicKey(), encryptionKey)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
decryptedMsg, err := encryption.Decrypt(encryptedMsg, encryptionKey.PublicKey(), decryptionKey)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(string(decryptedMsg)).To(BeEquivalentTo(msg))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("decrypting a protobuf message", func() {
|
||||||
|
Context("when it was encrypted with Wireguard keys", func() {
|
||||||
|
Specify("should be successful", func() {
|
||||||
|
|
||||||
|
protoMsg := &testprotos.TestMessage{Body: "message"}
|
||||||
|
encryptedMsg, err := encryption.EncryptMessage(decryptionKey.PublicKey(), encryptionKey, protoMsg)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
decryptedMsg := &testprotos.TestMessage{}
|
||||||
|
err = encryption.DecryptMessage(encryptionKey.PublicKey(), decryptionKey, encryptedMsg, decryptedMsg)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(decryptedMsg.GetBody()).To(BeEquivalentTo(protoMsg.GetBody()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
30
encryption/letsencrypt.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateCertManager wraps common logic of generating Let's encrypt certificate.
|
||||||
|
func CreateCertManager(datadir string, letsencryptDomain string) *autocert.Manager {
|
||||||
|
certDir := filepath.Join(datadir, "letsencrypt")
|
||||||
|
|
||||||
|
if _, err := os.Stat(certDir); os.IsNotExist(err) {
|
||||||
|
err = os.MkdirAll(certDir, os.ModeDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed creating Let's encrypt certdir: %s: %v", certDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("running with Let's encrypt with domain %s. Cert will be stored in %s", letsencryptDomain, certDir)
|
||||||
|
|
||||||
|
certManager := &autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
Cache: autocert.DirCache(certDir),
|
||||||
|
HostPolicy: autocert.HostWhitelist(letsencryptDomain),
|
||||||
|
}
|
||||||
|
|
||||||
|
return certManager
|
||||||
|
}
|
||||||
40
encryption/message.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
pb "github.com/golang/protobuf/proto" //nolint
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncryptMessage encrypts a body of the given protobuf Message
|
||||||
|
func EncryptMessage(remotePubKey wgtypes.Key, ourPrivateKey wgtypes.Key, message pb.Message) ([]byte, error) {
|
||||||
|
byteResp, err := pb.Marshal(message)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed marshalling message %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedBytes, err := Encrypt(byteResp, remotePubKey, ourPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed encrypting SyncResponse %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return encryptedBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptMessage decrypts an encrypted message into given protobuf Message
|
||||||
|
func DecryptMessage(remotePubKey wgtypes.Key, ourPrivateKey wgtypes.Key, encryptedMessage []byte, message pb.Message) error {
|
||||||
|
decrypted, err := Decrypt(encryptedMessage, remotePubKey, ourPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("error while decrypting Sync request message from peer %s", remotePubKey.String())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pb.Unmarshal(decrypted, message)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("error while umarshalling Sync request message from peer %s", remotePubKey.String())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
2
encryption/testprotos/generate.sh
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
protoc -I testprotos/ testprotos/testproto.proto --go_out=.
|
||||||
142
encryption/testprotos/testproto.pb.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.26.0
|
||||||
|
// protoc v3.12.4
|
||||||
|
// source: testproto.proto
|
||||||
|
|
||||||
|
package testprotos
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestMessage struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Body string `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TestMessage) Reset() {
|
||||||
|
*x = TestMessage{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_testproto_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TestMessage) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*TestMessage) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *TestMessage) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_testproto_proto_msgTypes[0]
|
||||||
|
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 TestMessage.ProtoReflect.Descriptor instead.
|
||||||
|
func (*TestMessage) Descriptor() ([]byte, []int) {
|
||||||
|
return file_testproto_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TestMessage) GetBody() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Body
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_testproto_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_testproto_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x0f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||||
|
0x6f, 0x12, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x21, 0x0a,
|
||||||
|
0x0b, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04,
|
||||||
|
0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79,
|
||||||
|
0x42, 0x0d, 0x5a, 0x0b, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62,
|
||||||
|
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_testproto_proto_rawDescOnce sync.Once
|
||||||
|
file_testproto_proto_rawDescData = file_testproto_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_testproto_proto_rawDescGZIP() []byte {
|
||||||
|
file_testproto_proto_rawDescOnce.Do(func() {
|
||||||
|
file_testproto_proto_rawDescData = protoimpl.X.CompressGZIP(file_testproto_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_testproto_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_testproto_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
|
var file_testproto_proto_goTypes = []interface{}{
|
||||||
|
(*TestMessage)(nil), // 0: testprotos.TestMessage
|
||||||
|
}
|
||||||
|
var file_testproto_proto_depIdxs = []int32{
|
||||||
|
0, // [0:0] is the sub-list for method output_type
|
||||||
|
0, // [0:0] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_testproto_proto_init() }
|
||||||
|
func file_testproto_proto_init() {
|
||||||
|
if File_testproto_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_testproto_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*TestMessage); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_testproto_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 1,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_testproto_proto_goTypes,
|
||||||
|
DependencyIndexes: file_testproto_proto_depIdxs,
|
||||||
|
MessageInfos: file_testproto_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_testproto_proto = out.File
|
||||||
|
file_testproto_proto_rawDesc = nil
|
||||||
|
file_testproto_proto_goTypes = nil
|
||||||
|
file_testproto_proto_depIdxs = nil
|
||||||
|
}
|
||||||
9
encryption/testprotos/testproto.proto
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option go_package = "/testprotos";
|
||||||
|
|
||||||
|
package testprotos;
|
||||||
|
|
||||||
|
message TestMessage {
|
||||||
|
string body = 1;
|
||||||
|
}
|
||||||
87
go.mod
@@ -1,18 +1,81 @@
|
|||||||
module github.com/wiretrustee/wiretrustee
|
module github.com/wiretrustee/wiretrustee
|
||||||
|
|
||||||
go 1.16
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cenkalti/backoff/v4 v4.1.0
|
github.com/cenkalti/backoff/v4 v4.1.2
|
||||||
github.com/golang/protobuf v1.4.3
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
github.com/google/nftables v0.0.0-20201230142148-715e31cb3c31
|
github.com/golang/protobuf v1.5.2
|
||||||
github.com/pion/ice/v2 v2.1.7
|
github.com/google/uuid v1.3.0
|
||||||
github.com/sirupsen/logrus v1.7.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/spf13/cobra v1.1.3
|
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.8.1
|
||||||
|
github.com/spf13/cobra v1.3.0
|
||||||
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/vishvananda/netlink v1.1.0
|
github.com/vishvananda/netlink v1.1.0
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df
|
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
|
||||||
golang.zx2c4.com/wireguard v0.0.20201118
|
golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
||||||
google.golang.org/grpc v1.32.0
|
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/getlantern/systray v1.2.0
|
||||||
|
github.com/magiconair/properties v1.8.5
|
||||||
|
github.com/rs/xid v1.3.0
|
||||||
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||||
|
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/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
|
||||||
|
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
|
||||||
|
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
|
||||||
|
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
|
||||||
|
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
|
||||||
|
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
|
||||||
|
github.com/go-stack/stack v1.8.0 // 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/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // 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
|
||||||
|
}
|
||||||