Compare commits
306 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
9554247d70 | ||
|
|
a9d3ce227a | ||
|
|
33e9ecfcb9 | ||
|
|
7446ef857b | ||
|
|
8adf92a957 | ||
|
|
d47d14a7d3 | ||
|
|
47933bcbfa | ||
|
|
7080309842 | ||
|
|
74f7da61cc | ||
|
|
123687c2f1 | ||
|
|
bbf57b064c | ||
|
|
8c1cf88e07 | ||
|
|
d08b61b31d | ||
|
|
48659ff4ac | ||
|
|
6b8465886d | ||
|
|
06ca0853b6 | ||
|
|
54e4e74883 | ||
|
|
682049b49c |
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
|
||||
|
||||
|
||||
34
.github/workflows/release.yml
vendored
@@ -14,11 +14,15 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # It is required for GoReleaser to work properly
|
||||
|
||||
- name: Generate syso with DLL
|
||||
run: bash -x wireguard_nt.sh
|
||||
working-directory: client
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.17
|
||||
-
|
||||
name: Cache Go modules
|
||||
uses: actions/cache@v1
|
||||
@@ -30,12 +34,36 @@ jobs:
|
||||
-
|
||||
name: Install modules
|
||||
run: go mod tidy
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to Docker hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
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.1
|
||||
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
||||
inputs: '{ "tag": "${{ github.ref }}" }'
|
||||
10
.gitignore
vendored
@@ -1,2 +1,10 @@
|
||||
.idea
|
||||
*.iml
|
||||
*.iml
|
||||
dist/
|
||||
.env
|
||||
conf.json
|
||||
http-cmds.sh
|
||||
infrastructure_files/management.json
|
||||
infrastructure_files/docker-compose.yml
|
||||
*.syso
|
||||
client/.distfiles/
|
||||
340
.goreleaser.yaml
@@ -1,30 +1,346 @@
|
||||
project_name: wiretrustee
|
||||
builds:
|
||||
- env: [CGO_ENABLED=0]
|
||||
- id: wiretrustee
|
||||
dir: client
|
||||
binary: wiretrustee
|
||||
env: [CGO_ENABLED=0]
|
||||
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- arm
|
||||
- amd64
|
||||
- arm64
|
||||
- mips
|
||||
gomips:
|
||||
- hardfloat
|
||||
- softfloat
|
||||
ignore:
|
||||
- goos: darwin
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
ldflags:
|
||||
- -s -w -X github.com/wiretrustee/wiretrustee/client/system.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
tags:
|
||||
- load_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:
|
||||
- maintainer: Wiretrustee <wiretrustee@wiretrustee.com>
|
||||
description: Wiretrustee project.
|
||||
- maintainer: Wiretrustee <dev@wiretrustee.com>
|
||||
description: Wiretrustee client.
|
||||
homepage: https://wiretrustee.com/
|
||||
id: deb
|
||||
builds:
|
||||
- wiretrustee
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
contents:
|
||||
- src: release_files/wiretrustee.service
|
||||
dst: /lib/systemd/system/wiretrustee.service
|
||||
|
||||
- src: release_files/wiretrustee.json
|
||||
dst: /etc/wiretrustee/wiretrustee.json
|
||||
type: "config|noreplace"
|
||||
|
||||
scripts:
|
||||
postinstall: "release_files/post_install.sh"
|
||||
preremove: "release_files/pre_remove.sh"
|
||||
|
||||
- maintainer: Wiretrustee <dev@wiretrustee.com>
|
||||
description: Wiretrustee client.
|
||||
homepage: https://wiretrustee.com/
|
||||
id: rpm
|
||||
builds:
|
||||
- wiretrustee
|
||||
formats:
|
||||
- rpm
|
||||
|
||||
scripts:
|
||||
postinstall: "release_files/post_install.sh"
|
||||
preremove: "release_files/pre_remove.sh"
|
||||
dockers:
|
||||
- image_templates:
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-amd64
|
||||
ids:
|
||||
- wiretrustee
|
||||
goarch: amd64
|
||||
use: buildx
|
||||
dockerfile: client/Dockerfile
|
||||
build_flag_templates:
|
||||
- "--platform=linux/amd64"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
- image_templates:
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-arm64v8
|
||||
ids:
|
||||
- wiretrustee
|
||||
goarch: arm64
|
||||
use: buildx
|
||||
dockerfile: client/Dockerfile
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm64"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
- image_templates:
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-arm
|
||||
ids:
|
||||
- wiretrustee
|
||||
goarch: arm
|
||||
goarm: 6
|
||||
use: buildx
|
||||
dockerfile: client/Dockerfile
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
- image_templates:
|
||||
- wiretrustee/signal:{{ .Version }}-amd64
|
||||
ids:
|
||||
- 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:
|
||||
- "--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 }}-debug-arm
|
||||
ids:
|
||||
- wiretrustee-mgmt
|
||||
goarch: arm
|
||||
goarm: 6
|
||||
use: buildx
|
||||
dockerfile: management/Dockerfile.debug
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||
docker_manifests:
|
||||
- name_template: wiretrustee/wiretrustee:{{ .Version }}
|
||||
image_templates:
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-arm64v8
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-arm
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/wiretrustee:latest
|
||||
image_templates:
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-arm64v8
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-arm
|
||||
- wiretrustee/wiretrustee:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/signal:{{ .Version }}
|
||||
image_templates:
|
||||
- wiretrustee/signal:{{ .Version }}-arm64v8
|
||||
- wiretrustee/signal:{{ .Version }}-arm
|
||||
- wiretrustee/signal:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/signal:latest
|
||||
image_templates:
|
||||
- wiretrustee/signal:{{ .Version }}-arm64v8
|
||||
- wiretrustee/signal:{{ .Version }}-arm
|
||||
- wiretrustee/signal:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/management:{{ .Version }}
|
||||
image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-arm64v8
|
||||
- wiretrustee/management:{{ .Version }}-arm
|
||||
- wiretrustee/management:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/management:latest
|
||||
image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-arm64v8
|
||||
- wiretrustee/management:{{ .Version }}-arm
|
||||
- wiretrustee/management:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/management:debug-latest
|
||||
image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-debug-arm64v8
|
||||
- wiretrustee/management:{{ .Version }}-debug-arm
|
||||
- wiretrustee/management:{{ .Version }}-debug-amd64
|
||||
|
||||
brews:
|
||||
-
|
||||
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
|
||||
|
||||
3
AUTHORS
Normal file
@@ -0,0 +1,3 @@
|
||||
Mikhail Bragin (https://github.com/braginini)
|
||||
Maycon Santos (https://github.com/mlsmaycon)
|
||||
Wiretrustee UG (haftungsbeschränkt)
|
||||
132
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
dev@wiretrustee.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
13
LICENSE
Normal file
@@ -0,0 +1,13 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
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:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
226
README.md
Normal file
@@ -0,0 +1,226 @@
|
||||
<div align="center">
|
||||
|
||||
<p align="center">
|
||||
<img width="250" src="docs/media/logo-full.png"/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/license-BSD--3-blue" />
|
||||
<img src="https://img.shields.io/docker/pulls/wiretrustee/management" />
|
||||
<img src="https://badgen.net/badge/Open%20Source%3F/Yes%21/blue?icon=github" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<strong>
|
||||
Start using Wiretrustee at <a href="https://app.wiretrustee.com/">app.wiretrustee.com</a>
|
||||
<br/>
|
||||
See <a href="https://docs.wiretrustee.com">Documentation</a>
|
||||
<br/>
|
||||
Join our <a href="https://join.slack.com/t/wiretrustee/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A">Slack channel</a>
|
||||
<br/>
|
||||
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
**Wiretrustee is an open-source VPN platform built on top of WireGuard® making it easy to create secure private networks for your organization or home.**
|
||||
|
||||
It requires zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth.
|
||||
|
||||
**Wiretrustee automates Wireguard-based networks, offering a management layer with:**
|
||||
* Centralized Peer IP management with a UI dashboard.
|
||||
* Encrypted peer-to-peer connections without a centralized VPN gateway.
|
||||
* Automatic Peer discovery and configuration.
|
||||
* UDP hole punching to establish peer-to-peer connections behind NAT, firewall, and without a public static IP.
|
||||
* Connection relay fallback in case a peer-to-peer connection is not possible.
|
||||
* Multitenancy (coming soon).
|
||||
* Client application SSO with MFA (coming soon).
|
||||
* Access Controls (coming soon).
|
||||
* Activity Monitoring (coming soon).
|
||||
* Private DNS (coming soon)
|
||||
|
||||
### Secure peer-to-peer VPN in minutes
|
||||
<p float="left" align="middle">
|
||||
<img src="docs/media/peerA.gif" width="400"/>
|
||||
<img src="docs/media/peerB.gif" width="400"/>
|
||||
</p>
|
||||
|
||||
**Note**: The `main` branch may be in an *unstable or even broken state* during development. For stable versions, see [releases](https://github.com/wiretrustee/wiretrustee/releases).
|
||||
|
||||
Hosted version:
|
||||
[https://app.wiretrustee.com/](https://app.wiretrustee.com/peers).
|
||||
|
||||
[UI Dashboard Repo](https://github.com/wiretrustee/wiretrustee-dashboard)
|
||||
|
||||
|
||||
### 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.
|
||||
* Peers negotiate connection through [Signal Service](signal/).
|
||||
* Signal Service uses public Wireguard keys to route messages between peers.
|
||||
Contents of the messages sent between peers through the signaling server are encrypted with Wireguard keys, making it impossible to inspect them.
|
||||
* Occasionally, the NAT traversal is unsuccessful due to strict NATs (e.g. mobile carrier-grade NAT). When this occurs the system falls back to the relay server (TURN), and a secure Wireguard tunnel is established via the TURN server. [Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in Wiretrustee setups.
|
||||
|
||||
<p float="left" align="middle">
|
||||
<img src="https://docs.wiretrustee.com/img/architecture/high-level-dia.png" width="700"/>
|
||||
</p>
|
||||
|
||||
|
||||
### Product Roadmap
|
||||
- [Public Roadmap](https://github.com/wiretrustee/wiretrustee/projects/2)
|
||||
- [Public Roadmap Progress Tracking](https://github.com/wiretrustee/wiretrustee/projects/1)
|
||||
|
||||
### Client Installation
|
||||
#### Linux
|
||||
|
||||
**APT/Debian**
|
||||
1. Add the repository:
|
||||
```shell
|
||||
sudo apt-get update
|
||||
sudo apt-get install ca-certificates curl gnupg -y
|
||||
curl -L https://pkgs.wiretrustee.com/debian/public.key | sudo apt-key add -
|
||||
echo 'deb https://pkgs.wiretrustee.com/debian stable main' | sudo tee /etc/apt/sources.list.d/wiretrustee.list
|
||||
```
|
||||
2. Install the package
|
||||
```shell
|
||||
sudo apt-get update
|
||||
sudo apt-get install wiretrustee
|
||||
```
|
||||
**RPM/Red hat**
|
||||
1. Add the repository:
|
||||
```shell
|
||||
cat <<EOF | sudo tee /etc/yum.repos.d/wiretrustee.repo
|
||||
[Wiretrustee]
|
||||
name=Wiretrustee
|
||||
baseurl=https://pkgs.wiretrustee.com/yum/
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
gpgkey=https://pkgs.wiretrustee.com/yum/repodata/repomd.xml.key
|
||||
repo_gpgcheck=1
|
||||
EOF
|
||||
```
|
||||
2. Install the package
|
||||
```shell
|
||||
sudo yum install wiretrustee
|
||||
```
|
||||
#### MACOS
|
||||
**Brew install**
|
||||
1. Download and install Brew at https://brew.sh/
|
||||
2. Install the client
|
||||
```shell
|
||||
brew install wiretrustee/client/wiretrustee
|
||||
```
|
||||
**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
|
||||
1. Login to the Management Service. You need to have a `setup key` in hand (see ).
|
||||
|
||||
For **Unix** systems:
|
||||
```shell
|
||||
sudo wiretrustee up --setup-key <SETUP KEY>
|
||||
```
|
||||
For **Windows** systems, start powershell as administrator and:
|
||||
```shell
|
||||
wiretrustee up --setup-key <SETUP KEY>
|
||||
```
|
||||
For **Docker**, you can run with the following command:
|
||||
```shell
|
||||
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
|
||||
Wiretrustee uses [Auth0](https://auth0.com) for user authentication and authorization, therefore you will need to create a free account
|
||||
and configure Auth0 variables in the compose file (dashboard) and in the management config file.
|
||||
We chose Auth0 to "outsource" the user management part of our platform because we believe that implementing a proper user auth is not a trivial task and requires a significant amount of time to make it right. We focused on connectivity instead.
|
||||
It is worth mentioning that the dependency on Auth0 is the only one that cannot be self-hosted.
|
||||
|
||||
Configuring Wiretrustee Auth0 integration:
|
||||
- check [How to run](https://github.com/wiretrustee/wiretrustee-dashboard#how-to-run) to obtain Auth0 environment variables for UI Dashboard
|
||||
- set these variables in the [environment section of the docker-compose file](https://github.com/wiretrustee/wiretrustee/blob/main/infrastructure_files/docker-compose.yml)
|
||||
- check [Auth0 Golang API Guide](https://auth0.com/docs/quickstart/backend/golang) to obtain ```AuthIssuer```, ```AuthAudience```, and ```AuthKeysLocation```
|
||||
- set these properties in the [management config files](https://github.com/wiretrustee/wiretrustee/blob/main/infrastructure_files/management.json#L33)
|
||||
|
||||
|
||||
Under infrastructure_files we have a docker-compose example to run Dashboard, Wiretrustee Management and Signal services, plus an instance of [Coturn](https://github.com/coturn/coturn), it also provides a turnserver.conf file as a simple example of Coturn configuration.
|
||||
You can edit the turnserver.conf file and change its Realm setting (defaults to wiretrustee.com) to your own domain and user setting (defaults to username1:password1) to **proper credentials**.
|
||||
|
||||
The example is set to use the official images from Wiretrustee and Coturn; you can find our documentation to run the signal server in docker in [Running the Signal service](#running-the-signal-service), the management in [Management](./management/README.md), and the Coturn official documentation [here](https://hub.docker.com/r/coturn/coturn).
|
||||
|
||||
> Run Coturn at your own risk, we are just providing an example, be sure to follow security best practices and to configure proper credentials as this service can be exploited and you may face large data transfer charges.
|
||||
|
||||
Also, if you have an SSL certificate for Coturn, you can modify the docker-compose.yml file to point to its files in your host machine and then switch the domain name to your own SSL domain. If you don't already have an SSL certificate, you can follow [Certbot's](https://certbot.eff.org/docs/intro.html) official documentation
|
||||
to generate one from [Let’s Encrypt](https://letsencrypt.org/), or, we found that the example provided by [BigBlueButton](https://docs.bigbluebutton.org/2.2/setup-turn-server.html#generating-tls-certificates) covers the basics to configure Coturn with Let's Encrypt certs.
|
||||
> The Wiretrustee Management service can generate and maintain the certificates automatically, all you need to do is run the service in a host with a public IP, configure a valid DNS record pointing to that IP and uncomment the 443 ports and command lines in the docker-compose.yml file.
|
||||
|
||||
Simple docker-composer execution:
|
||||
````shell
|
||||
cd infrastructure_files
|
||||
docker-compose up -d
|
||||
````
|
||||
You can check logs by running:
|
||||
````shell
|
||||
cd infrastructure_files
|
||||
docker-compose logs signal
|
||||
docker-compose logs management
|
||||
docker-compose logs coturn
|
||||
````
|
||||
If you need to stop the services, run the following:
|
||||
````shell
|
||||
cd infrastructure_files
|
||||
docker-compose down
|
||||
````
|
||||
|
||||
|
||||
### Legal
|
||||
[WireGuard](https://wireguard.com/) is a registered trademark of Jason A. Donenfeld.
|
||||
|
||||
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
|
||||
175
client/cmd/login.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
"github.com/wiretrustee/wiretrustee/client/system"
|
||||
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
||||
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var (
|
||||
loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "login to the Wiretrustee Management Service (first run)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
var backOff = &backoff.ExponentialBackOff{
|
||||
InitialInterval: time.Second,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 2 * time.Second,
|
||||
MaxElapsedTime: time.Second * 10,
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
|
||||
loginOp := func() error {
|
||||
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := internal.GetConfig(managementURL, configPath, preSharedKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed getting config %s %v", configPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
//validate our peer's Wireguard PRIVATE key
|
||||
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
mgmTlsEnabled := false
|
||||
if config.ManagementURL.Scheme == "https" {
|
||||
mgmTlsEnabled = true
|
||||
}
|
||||
|
||||
log.Debugf("connecting to Management Service %s", config.ManagementURL.String())
|
||||
mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||
if err != nil {
|
||||
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
|
||||
return err
|
||||
}
|
||||
log.Debugf("connected to management Service %s", config.ManagementURL.String())
|
||||
|
||||
serverKey, err := mgmClient.GetServerPublicKey()
|
||||
if err != nil {
|
||||
log.Errorf("failed while getting Management Service public key: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = loginPeer(*serverKey, mgmClient, setupKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed logging-in peer on Management Service : %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = mgmClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Management Service client: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
var err error
|
||||
if setupKey == "" {
|
||||
setupKey, err = promptPeerSetupKey()
|
||||
if err != nil {
|
||||
log.Errorf("failed getting setup key from user: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// promptPeerSetupKey prompts user to enter Setup Key
|
||||
func promptPeerSetupKey() (string, error) {
|
||||
fmt.Print("Enter setup key: ")
|
||||
|
||||
s := bufio.NewScanner(os.Stdin)
|
||||
for s.Scan() {
|
||||
input := s.Text()
|
||||
if input != "" {
|
||||
return input, nil
|
||||
}
|
||||
fmt.Println("Specified key is empty, try again:")
|
||||
|
||||
}
|
||||
|
||||
return "", s.Err()
|
||||
}
|
||||
69
client/cmd/login_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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(config, t)
|
||||
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,
|
||||
"--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")
|
||||
}
|
||||
}
|
||||
101
client/cmd/root.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
configPath string
|
||||
defaultConfigPath string
|
||||
logLevel string
|
||||
defaultLogFile string
|
||||
logFile 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"
|
||||
}
|
||||
|
||||
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(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
|
||||
}
|
||||
36
client/cmd/service.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/kardianos/service"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type program struct {
|
||||
cmd *cobra.Command
|
||||
args []string
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
)
|
||||
152
client/cmd/service_controller.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/kardianos/service"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (p *program) Start(service.Service) error {
|
||||
|
||||
// Start should not block. Do the actual work async.
|
||||
log.Info("starting service") //nolint
|
||||
go func() {
|
||||
err := runClient()
|
||||
if err != nil {
|
||||
log.Errorf("stopped Wiretrustee client app due to error: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *program) Stop(service.Service) error {
|
||||
go func() {
|
||||
stopCh <- 1
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-cleanupCh:
|
||||
case <-time.After(time.Second * 10):
|
||||
log.Warnf("failed waiting for service cleanup, terminating")
|
||||
}
|
||||
log.Info("stopped Wiretrustee service") //nolint
|
||||
return nil
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
prg := &program{
|
||||
cmd: cmd,
|
||||
args: args,
|
||||
}
|
||||
|
||||
s, err := newSVC(prg, 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(&program{}, 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(&program{}, 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(&program{}, 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")
|
||||
},
|
||||
}
|
||||
)
|
||||
69
client/cmd/service_installer.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
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(&program{}, 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(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = s.Uninstall()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Println("Wiretrustee has been uninstalled")
|
||||
},
|
||||
}
|
||||
)
|
||||
56
client/cmd/testutil.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
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"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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(config *mgmt.Config, t *testing.T) (*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
|
||||
}
|
||||
}()
|
||||
|
||||
return s, lis
|
||||
}
|
||||
238
client/cmd/up.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/kardianos/service"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
||||
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
signal "github.com/wiretrustee/wiretrustee/signal/client"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var (
|
||||
upCmd = &cobra.Command{
|
||||
Use: "up",
|
||||
Short: "install, login and start wiretrustee client",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
SetFlagsFromEnvVars()
|
||||
|
||||
err := loginCmd.RunE(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if logFile == "console" {
|
||||
SetupCloseHandler()
|
||||
return runClient()
|
||||
}
|
||||
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return err
|
||||
}
|
||||
|
||||
srvStatus, err := s.Status()
|
||||
if err != nil {
|
||||
if err == service.ErrNotInstalled {
|
||||
log.Infof("%s. Installing it now", err.Error())
|
||||
e := installCmd.RunE(cmd, args)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
} else {
|
||||
log.Warnf("failed retrieving service status: %v", err)
|
||||
}
|
||||
}
|
||||
if srvStatus == service.StatusRunning {
|
||||
stopCmd.Run(cmd, args)
|
||||
}
|
||||
return startCmd.RunE(cmd, args)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// createEngineConfig converts configuration received from Management Service to EngineConfig
|
||||
func createEngineConfig(key wgtypes.Key, config *internal.Config, peerConfig *mgmProto.PeerConfig) (*internal.EngineConfig, error) {
|
||||
iFaceBlackList := make(map[string]struct{})
|
||||
for i := 0; i < len(config.IFaceBlackList); i += 2 {
|
||||
iFaceBlackList[config.IFaceBlackList[i]] = struct{}{}
|
||||
}
|
||||
|
||||
engineConf := &internal.EngineConfig{
|
||||
WgIfaceName: config.WgIface,
|
||||
WgAddr: peerConfig.Address,
|
||||
IFaceBlackList: iFaceBlackList,
|
||||
WgPrivateKey: key,
|
||||
WgPort: internal.WgPort,
|
||||
}
|
||||
|
||||
if config.PreSharedKey != "" {
|
||||
preSharedKey, err := wgtypes.ParseKey(config.PreSharedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
engineConf.PreSharedKey = &preSharedKey
|
||||
}
|
||||
|
||||
return engineConf, nil
|
||||
}
|
||||
|
||||
// connectToSignal creates Signal Service client and established a connection
|
||||
func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, ourPrivateKey wgtypes.Key) (*signal.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
|
||||
}
|
||||
|
||||
func runClient() error {
|
||||
var backOff = &backoff.ExponentialBackOff{
|
||||
InitialInterval: time.Second,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 10 * time.Second,
|
||||
MaxElapsedTime: 24 * 3 * time.Hour, //stop the client after 3 days trying (must be a huge problem, e.g permission denied)
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
|
||||
operation := func() error {
|
||||
|
||||
config, err := internal.ReadConfig(managementURL, configPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed reading config %s %v", configPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
//validate our peer's Wireguard PRIVATE key
|
||||
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
mgmTlsEnabled := false
|
||||
if config.ManagementURL.Scheme == "https" {
|
||||
mgmTlsEnabled = true
|
||||
}
|
||||
|
||||
// connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config
|
||||
mgmClient, loginResp, err := connectToManagement(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
|
||||
signalClient, err := connectToSignal(ctx, loginResp.GetWiretrusteeConfig(), myPrivateKey)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
peerConfig := loginResp.GetPeerConfig()
|
||||
|
||||
engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
engine := internal.NewEngine(signalClient, mgmClient, engineConfig, cancel, ctx)
|
||||
err = engine.Start()
|
||||
if err != nil {
|
||||
log.Errorf("error while starting Wiretrustee Connection Engine: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Print("Wiretrustee engine started, my IP is: ", peerConfig.Address)
|
||||
|
||||
select {
|
||||
case <-stopCh:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
backOff.Reset()
|
||||
|
||||
err = mgmClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Management Service client %v", err)
|
||||
return err
|
||||
}
|
||||
err = signalClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Signal Service client %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = engine.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("failed stopping engine %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
cleanupCh <- struct{}{}
|
||||
}()
|
||||
|
||||
log.Info("stopped Wiretrustee client")
|
||||
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, backOff)
|
||||
if err != nil {
|
||||
log.Errorf("exiting client retry loop due to unrecoverable error: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
87
client/cmd/up_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var signalAddr 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(config, t)
|
||||
mgmAddr = mgmLis.Addr().String()
|
||||
|
||||
}
|
||||
|
||||
func TestUp(t *testing.T) {
|
||||
|
||||
//defer iface.Close("wt0")
|
||||
|
||||
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() {
|
||||
err = rootCmd.Execute()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error while running up command, got %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
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()
|
||||
}
|
||||
570
client/internal/engine.go
Normal file
@@ -0,0 +1,570 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"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
|
||||
const PeerConnectionTimeoutMin = 30000 //ms
|
||||
|
||||
const WgPort = 51820
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 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(signalClient signal.Client, mgmClient mgm.Client, config *EngineConfig, cancel context.CancelFunc, ctx context.Context) *Engine {
|
||||
return &Engine{
|
||||
signal: signalClient,
|
||||
mgmClient: mgmClient,
|
||||
peerConns: map[string]*peer.Conn{},
|
||||
syncMsgMux: &sync.Mutex{},
|
||||
config: config,
|
||||
STUNs: []*ice.URL{},
|
||||
TURNs: []*ice.URL{},
|
||||
cancel: cancel,
|
||||
ctx: ctx,
|
||||
networkSerial: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Engine) Stop() error {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
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,
|
||||
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
|
||||
e.cancel()
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
e.signal.WaitStreamConnected()
|
||||
}
|
||||
466
client/internal/engine_test.go
Normal file
@@ -0,0 +1,466 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/client/system"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/client"
|
||||
mgmtProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
"github.com/wiretrustee/wiretrustee/management/server"
|
||||
signal "github.com/wiretrustee/wiretrustee/signal/client"
|
||||
"github.com/wiretrustee/wiretrustee/signal/proto"
|
||||
signalServer "github.com/wiretrustee/wiretrustee/signal/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
var (
|
||||
kaep = keepalive.EnforcementPolicy{
|
||||
MinTime: 15 * time.Second,
|
||||
PermitWithoutStream: true,
|
||||
}
|
||||
|
||||
kasp = keepalive.ServerParameters{
|
||||
MaxConnectionIdle: 15 * time.Second,
|
||||
MaxConnectionAgeGrace: 5 * time.Second,
|
||||
Time: 5 * time.Second,
|
||||
Timeout: 2 * time.Second,
|
||||
}
|
||||
)
|
||||
|
||||
func TestEngine_UpdateNetworkMap(t *testing.T) {
|
||||
|
||||
// test setup
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
engine := NewEngine(&signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||
WgIfaceName: "utun100",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
}, cancel, ctx)
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
networkMap *mgmtProto.NetworkMap
|
||||
|
||||
expectedLen int
|
||||
expectedPeers []string
|
||||
expectedSerial uint64
|
||||
}
|
||||
|
||||
peer1 := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "RRHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
AllowedIps: []string{"100.64.0.10/24"},
|
||||
}
|
||||
|
||||
peer2 := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "LLHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
AllowedIps: []string{"100.64.0.11/24"},
|
||||
}
|
||||
|
||||
peer3 := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "GGHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
AllowedIps: []string{"100.64.0.12/24"},
|
||||
}
|
||||
|
||||
case1 := testCase{
|
||||
name: "input with a new peer to add",
|
||||
networkMap: &mgmtProto.NetworkMap{
|
||||
Serial: 1,
|
||||
PeerConfig: nil,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{
|
||||
peer1,
|
||||
},
|
||||
RemotePeersIsEmpty: false,
|
||||
},
|
||||
expectedLen: 1,
|
||||
expectedPeers: []string{peer1.GetWgPubKey()},
|
||||
expectedSerial: 1,
|
||||
}
|
||||
|
||||
// 2nd case - one extra peer added and network map has Serial grater than local => apply the update
|
||||
case2 := testCase{
|
||||
name: "input with an old peer and a new peer to add",
|
||||
networkMap: &mgmtProto.NetworkMap{
|
||||
Serial: 2,
|
||||
PeerConfig: nil,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{
|
||||
peer1, peer2,
|
||||
},
|
||||
RemotePeersIsEmpty: false,
|
||||
},
|
||||
expectedLen: 2,
|
||||
expectedPeers: []string{peer1.GetWgPubKey(), peer2.GetWgPubKey()},
|
||||
expectedSerial: 2,
|
||||
}
|
||||
|
||||
case3 := testCase{
|
||||
name: "input with outdated (old) update to ignore",
|
||||
networkMap: &mgmtProto.NetworkMap{
|
||||
Serial: 0,
|
||||
PeerConfig: nil,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{
|
||||
peer1, peer2, peer3,
|
||||
},
|
||||
RemotePeersIsEmpty: false,
|
||||
},
|
||||
expectedLen: 2,
|
||||
expectedPeers: []string{peer1.GetWgPubKey(), peer2.GetWgPubKey()},
|
||||
expectedSerial: 2,
|
||||
}
|
||||
|
||||
case4 := testCase{
|
||||
name: "input with one peer to remove and one new to add",
|
||||
networkMap: &mgmtProto.NetworkMap{
|
||||
Serial: 4,
|
||||
PeerConfig: nil,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{
|
||||
peer2, peer3,
|
||||
},
|
||||
RemotePeersIsEmpty: false,
|
||||
},
|
||||
expectedLen: 2,
|
||||
expectedPeers: []string{peer2.GetWgPubKey(), peer3.GetWgPubKey()},
|
||||
expectedSerial: 4,
|
||||
}
|
||||
|
||||
case5 := testCase{
|
||||
name: "input with all peers to remove",
|
||||
networkMap: &mgmtProto.NetworkMap{
|
||||
Serial: 5,
|
||||
PeerConfig: nil,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{},
|
||||
RemotePeersIsEmpty: true,
|
||||
},
|
||||
expectedLen: 0,
|
||||
expectedPeers: nil,
|
||||
expectedSerial: 5,
|
||||
}
|
||||
|
||||
for _, c := range []testCase{case1, case2, case3, case4, case5} {
|
||||
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
err = engine.updateNetworkMap(c.networkMap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(engine.peerConns) != c.expectedLen {
|
||||
t.Errorf("expecting Engine.peerConns to be of size %d, got %d", c.expectedLen, len(engine.peerConns))
|
||||
}
|
||||
|
||||
if engine.networkSerial != c.expectedSerial {
|
||||
t.Errorf("expecting Engine.networkSerial to be equal to %d, actual %d", c.expectedSerial, engine.networkSerial)
|
||||
}
|
||||
|
||||
for _, p := range c.expectedPeers {
|
||||
if _, ok := engine.peerConns[p]; !ok {
|
||||
t.Errorf("expecting Engine.peerConns to contain peer %s", p)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEngine_Sync(t *testing.T) {
|
||||
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// feed updates to Engine via mocked Management client
|
||||
updates := make(chan *mgmtProto.SyncResponse)
|
||||
defer close(updates)
|
||||
syncFunc := func(msgHandler func(msg *mgmtProto.SyncResponse) error) error {
|
||||
|
||||
for msg := range updates {
|
||||
err := msgHandler(msg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
engine := NewEngine(&signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, &EngineConfig{
|
||||
WgIfaceName: "utun100",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
}, cancel, ctx)
|
||||
|
||||
defer func() {
|
||||
err := engine.Stop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
err = engine.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
peer1 := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "RRHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
AllowedIps: []string{"100.64.0.10/24"},
|
||||
}
|
||||
peer2 := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "LLHf3Ma6z6mdLbriAJbqhX9+nM/B71lgw2+91q3LlhU=",
|
||||
AllowedIps: []string{"100.64.0.11/24"},
|
||||
}
|
||||
peer3 := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "GGHf3Ma6z6mdLbriAJbqhX9+nM/B71lgw2+91q3LlhU=",
|
||||
AllowedIps: []string{"100.64.0.12/24"},
|
||||
}
|
||||
// 1st update with just 1 peer and serial larger than the current serial of the engine => apply update
|
||||
updates <- &mgmtProto.SyncResponse{
|
||||
NetworkMap: &mgmtProto.NetworkMap{
|
||||
Serial: 10,
|
||||
PeerConfig: nil,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{peer1, peer2, peer3},
|
||||
RemotePeersIsEmpty: false,
|
||||
},
|
||||
}
|
||||
|
||||
timeout := time.After(time.Second * 2)
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatalf("timeout while waiting for test to finish")
|
||||
default:
|
||||
}
|
||||
|
||||
if len(engine.GetPeers()) == 3 && engine.networkSerial == 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEngine_MultiplePeers(t *testing.T) {
|
||||
|
||||
//log.SetLevel(log.DebugLevel)
|
||||
|
||||
dir := t.TempDir()
|
||||
|
||||
err := util.CopyFileContents("../testdata/store.json", filepath.Join(dir, "store.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = os.Remove(filepath.Join(dir, "store.json")) //nolint
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
sport := 10010
|
||||
sigServer, err := startSignal(sport)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer sigServer.Stop()
|
||||
mport := 33081
|
||||
mgmtServer, err := startManagement(mport, dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer mgmtServer.GracefulStop()
|
||||
|
||||
setupKey := "A2C8E62B-38F5-4553-B31E-DD66C696CEBB"
|
||||
|
||||
mu := sync.Mutex{}
|
||||
engines := []*Engine{}
|
||||
numPeers := 10
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(numPeers)
|
||||
// create and start peers
|
||||
for i := 0; i < numPeers; i++ {
|
||||
j := i
|
||||
go func() {
|
||||
engine, err := createEngine(ctx, cancel, setupKey, j, mport, sport)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
engine.Start() //nolint
|
||||
engines = append(engines, engine)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
// wait until all have been created and started
|
||||
wg.Wait()
|
||||
// check whether all the peer have expected peers connected
|
||||
|
||||
expectedConnected := numPeers * (numPeers - 1)
|
||||
// adjust according to timeouts
|
||||
timeout := 50 * time.Second
|
||||
timeoutChan := time.After(timeout)
|
||||
for {
|
||||
select {
|
||||
case <-timeoutChan:
|
||||
t.Fatalf("waiting for expected connections timeout after %s", timeout.String())
|
||||
return
|
||||
default:
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
totalConnected := 0
|
||||
for _, engine := range engines {
|
||||
totalConnected = totalConnected + len(engine.GetConnectedPeers())
|
||||
}
|
||||
if totalConnected == expectedConnected {
|
||||
log.Debugf("total connected=%d", totalConnected)
|
||||
break
|
||||
}
|
||||
log.Infof("total connected=%d", totalConnected)
|
||||
}
|
||||
|
||||
// cleanup test
|
||||
for _, peerEngine := range engines {
|
||||
errStop := peerEngine.mgmClient.Close()
|
||||
if errStop != nil {
|
||||
log.Infoln("got error trying to close management clients from engine: ", errStop)
|
||||
}
|
||||
errStop = peerEngine.Stop()
|
||||
if errStop != nil {
|
||||
log.Infoln("got error trying to close testing peers engine: ", errStop)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey string, i int, mport int, sport int) (*Engine, error) {
|
||||
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mgmtClient, err := mgmt.NewClient(ctx, fmt.Sprintf("localhost:%d", mport), key, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signalClient, err := signal.NewClient(ctx, fmt.Sprintf("localhost:%d", sport), key, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicKey, err := mgmtClient.GetServerPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := system.GetInfo()
|
||||
resp, err := mgmtClient.Register(*publicKey, setupKey, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ifaceName string
|
||||
if runtime.GOOS == "darwin" {
|
||||
ifaceName = fmt.Sprintf("utun1%d", i)
|
||||
} else {
|
||||
ifaceName = fmt.Sprintf("wt%d", i)
|
||||
}
|
||||
|
||||
conf := &EngineConfig{
|
||||
WgIfaceName: ifaceName,
|
||||
WgAddr: resp.PeerConfig.Address,
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100 + i,
|
||||
}
|
||||
|
||||
return NewEngine(signalClient, mgmtClient, conf, cancel, ctx), nil
|
||||
}
|
||||
|
||||
func startSignal(port int) (*grpc.Server, error) {
|
||||
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
proto.RegisterSignalExchangeServer(s, signalServer.NewServer())
|
||||
|
||||
go func() {
|
||||
if err = s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func startManagement(port int, dataDir string) (*grpc.Server, error) {
|
||||
|
||||
config := &server.Config{
|
||||
Stuns: []*server.Host{},
|
||||
TURNConfig: &server.TURNConfig{},
|
||||
Signal: &server.Host{
|
||||
Proto: "http",
|
||||
URI: "localhost:10000",
|
||||
},
|
||||
Datadir: dataDir,
|
||||
HttpConfig: nil,
|
||||
}
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||
store, err := server.NewStore(config.Datadir)
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
||||
}
|
||||
peersUpdateManager := server.NewPeersUpdateManager()
|
||||
accountManager := server.NewManager(store, peersUpdateManager, nil)
|
||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mgmtProto.RegisterManagementServiceServer(s, mgmtServer)
|
||||
go func() {
|
||||
if err = s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
430
client/internal/peer/conn.go
Normal file
@@ -0,0 +1,430 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/pion/ice/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal/proxy"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if len(blackListMap) == 0 {
|
||||
return true
|
||||
}
|
||||
_, ok := blackListMap[iFace]
|
||||
return !ok
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.agent.OnCandidate(conn.onICECandidate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.agent.OnConnectionStateChange(conn.onICEConnectionStateChange)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.agent.OnSelectedCandidatePairChange(conn.onICESelectedCandidatePair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open opens connection to the remote peer starting ICE candidate gathering process.
|
||||
// Blocks until connection has been closed or connection timeout.
|
||||
// ConnStatus will be set accordingly
|
||||
func (conn *Conn) Open() error {
|
||||
log.Debugf("trying to connect to peer %s", conn.config.Key)
|
||||
|
||||
defer func() {
|
||||
err := conn.cleanup()
|
||||
if err != nil {
|
||||
log.Errorf("error while cleaning up peer connection %s: %v", conn.config.Key, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
err := conn.reCreateAgent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.sendOffer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("connection offer sent to peer %s, waiting for the confirmation", conn.config.Key)
|
||||
|
||||
// Only continue once we got a connection confirmation from the remote peer.
|
||||
// The connection timeout could have happened before a confirmation received from the remote.
|
||||
// The connection could have also been closed externally (e.g. when we received an update from the management that peer shouldn't be connected)
|
||||
var remoteCredentials IceCredentials
|
||||
select {
|
||||
case remoteCredentials = <-conn.remoteOffersCh:
|
||||
// received confirmation from the remote peer -> ready to proceed
|
||||
err = conn.sendAnswer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case remoteCredentials = <-conn.remoteAnswerCh:
|
||||
case <-time.After(conn.config.Timeout):
|
||||
return NewConnectionTimeoutError(conn.config.Key, conn.config.Timeout)
|
||||
case <-conn.closeCh:
|
||||
// closed externally
|
||||
return NewConnectionClosedError(conn.config.Key)
|
||||
}
|
||||
|
||||
log.Debugf("received connection confirmation from peer %s", conn.config.Key)
|
||||
|
||||
//at this point we received offer/answer and we are ready to gather candidates
|
||||
conn.mu.Lock()
|
||||
conn.status = StatusConnecting
|
||||
conn.ctx, conn.notifyDisconnected = context.WithCancel(context.Background())
|
||||
defer conn.notifyDisconnected()
|
||||
conn.mu.Unlock()
|
||||
|
||||
err = conn.agent.GatherCandidates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// will block until connection succeeded
|
||||
// but it won't release if ICE Agent went into Disconnected or Failed state,
|
||||
// so we have to cancel it with the provided context once agent detected a broken connection
|
||||
isControlling := conn.config.LocalKey > conn.config.Key
|
||||
var remoteConn *ice.Conn
|
||||
if isControlling {
|
||||
remoteConn, err = conn.agent.Dial(conn.ctx, remoteCredentials.UFrag, remoteCredentials.Pwd)
|
||||
} else {
|
||||
remoteConn, err = conn.agent.Accept(conn.ctx, remoteCredentials.UFrag, remoteCredentials.Pwd)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// the connection has been established successfully so we are ready to start the proxy
|
||||
err = conn.startProxy(remoteConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("connected to peer %s [laddr <-> raddr] [%s <-> %s]", conn.config.Key, remoteConn.LocalAddr().String(), remoteConn.RemoteAddr().String())
|
||||
|
||||
// wait until connection disconnected or has been closed externally (upper layer, e.g. engine)
|
||||
select {
|
||||
case <-conn.closeCh:
|
||||
// closed externally
|
||||
return NewConnectionClosedError(conn.config.Key)
|
||||
case <-conn.ctx.Done():
|
||||
// disconnected from the remote peer
|
||||
return NewConnectionDisconnectedError(conn.config.Key)
|
||||
}
|
||||
}
|
||||
|
||||
// startProxy starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected
|
||||
func (conn *Conn) startProxy(remoteConn net.Conn) error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
conn.proxy = proxy.NewWireguardProxy(conn.config.ProxyConfig)
|
||||
err := conn.proxy.Start(remoteConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.status = StatusConnected
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanup closes all open resources and sets status to StatusDisconnected
|
||||
func (conn *Conn) cleanup() error {
|
||||
log.Debugf("trying to cleanup %s", conn.config.Key)
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
if conn.agent != nil {
|
||||
err := conn.agent.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.agent = nil
|
||||
}
|
||||
|
||||
if conn.proxy != nil {
|
||||
err := conn.proxy.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.proxy = nil
|
||||
}
|
||||
|
||||
if conn.notifyDisconnected != nil {
|
||||
conn.notifyDisconnected()
|
||||
conn.notifyDisconnected = nil
|
||||
}
|
||||
|
||||
conn.status = StatusDisconnected
|
||||
|
||||
log.Debugf("cleaned up connection to peer %s", conn.config.Key)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSignalOffer sets a handler function to be triggered by Conn when a new connection offer has to be signalled to the remote peer
|
||||
func (conn *Conn) SetSignalOffer(handler func(uFrag string, pwd string) error) {
|
||||
conn.signalOffer = handler
|
||||
}
|
||||
|
||||
// SetSignalAnswer sets a handler function to be triggered by Conn when a new connection answer has to be signalled to the remote peer
|
||||
func (conn *Conn) SetSignalAnswer(handler func(uFrag string, pwd string) error) {
|
||||
conn.signalAnswer = handler
|
||||
}
|
||||
|
||||
// SetSignalCandidate sets a handler function to be triggered by Conn when a new ICE local connection candidate has to be signalled to the remote peer
|
||||
func (conn *Conn) SetSignalCandidate(handler func(candidate ice.Candidate) error) {
|
||||
conn.signalCandidate = handler
|
||||
}
|
||||
|
||||
// onICECandidate is a callback attached to an ICE Agent to receive new local connection candidates
|
||||
// and then signals them to the remote peer
|
||||
func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
||||
if candidate != nil {
|
||||
//log.Debugf("discovered local candidate %s", candidate.String())
|
||||
go func() {
|
||||
err := conn.signalCandidate(candidate)
|
||||
if err != nil {
|
||||
log.Errorf("failed signaling candidate to the remote peer %s %s", conn.config.Key, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *Conn) onICESelectedCandidatePair(c1 ice.Candidate, c2 ice.Candidate) {
|
||||
log.Debugf("selected candidate pair [local <-> remote] -> [%s <-> %s], peer %s", conn.config.Key,
|
||||
c1.String(), c2.String())
|
||||
}
|
||||
|
||||
// onICEConnectionStateChange registers callback of an ICE Agent to track connection state
|
||||
func (conn *Conn) onICEConnectionStateChange(state ice.ConnectionState) {
|
||||
log.Debugf("peer %s ICE ConnectionState has changed to %s", conn.config.Key, state.String())
|
||||
if state == ice.ConnectionStateFailed || state == ice.ConnectionStateDisconnected {
|
||||
conn.notifyDisconnected()
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *Conn) sendAnswer() error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
localUFrag, localPwd, err := conn.agent.GetLocalUserCredentials()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("sending asnwer to %s", conn.config.Key)
|
||||
err = conn.signalAnswer(localUFrag, localPwd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendOffer prepares local user credentials and signals them to the remote peer
|
||||
func (conn *Conn) sendOffer() error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
localUFrag, localPwd, err := conn.agent.GetLocalUserCredentials()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = conn.signalOffer(localUFrag, localPwd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes this peer Conn issuing a close event to the Conn closeCh
|
||||
func (conn *Conn) Close() error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
select {
|
||||
case conn.closeCh <- struct{}{}:
|
||||
return nil
|
||||
default:
|
||||
// probably could happen when peer has been added and removed right after not even starting to connect
|
||||
// todo further investigate
|
||||
// this really happens due to unordered messages coming from management
|
||||
// more importantly it causes inconsistency -> 2 Conn objects for the same peer
|
||||
// e.g. this flow:
|
||||
// update from management has peers: [1,2,3,4]
|
||||
// engine creates a Conn for peers: [1,2,3,4] and schedules Open in ~1sec
|
||||
// before conn.Open() another update from management arrives with peers: [1,2,3]
|
||||
// engine removes peer 4 and calls conn.Close() which does nothing (this default clause)
|
||||
// before conn.Open() another update from management arrives with peers: [1,2,3,4,5]
|
||||
// engine adds a new Conn for 4 and 5
|
||||
// therefore peer 4 has 2 Conn objects
|
||||
log.Warnf("closing not started coonection %s", conn.config.Key)
|
||||
return NewConnectionAlreadyClosed(conn.config.Key)
|
||||
}
|
||||
}
|
||||
|
||||
// Status returns current status of the Conn
|
||||
func (conn *Conn) Status() ConnStatus {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
return conn.status
|
||||
}
|
||||
|
||||
// OnRemoteOffer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
|
||||
// doesn't block, discards the message if connection wasn't ready
|
||||
func (conn *Conn) OnRemoteOffer(remoteAuth IceCredentials) bool {
|
||||
log.Debugf("OnRemoteOffer from peer %s on status %s", conn.config.Key, conn.status.String())
|
||||
|
||||
select {
|
||||
case conn.remoteOffersCh <- remoteAuth:
|
||||
return true
|
||||
default:
|
||||
log.Debugf("OnRemoteOffer skipping message from peer %s on status %s because is not ready", conn.config.Key, conn.status.String())
|
||||
//connection might not be ready yet to receive so we ignore the message
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// OnRemoteAnswer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
|
||||
// doesn't block, discards the message if connection wasn't ready
|
||||
func (conn *Conn) OnRemoteAnswer(remoteAuth IceCredentials) bool {
|
||||
log.Debugf("OnRemoteAnswer from peer %s on status %s", conn.config.Key, conn.status.String())
|
||||
|
||||
select {
|
||||
case conn.remoteAnswerCh <- remoteAuth:
|
||||
return true
|
||||
default:
|
||||
//connection might not be ready yet to receive so we ignore the message
|
||||
log.Debugf("OnRemoteAnswer skipping message from peer %s on status %s because is not ready", conn.config.Key, conn.status.String())
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// OnRemoteCandidate Handles ICE connection Candidate provided by the remote peer.
|
||||
func (conn *Conn) OnRemoteCandidate(candidate ice.Candidate) {
|
||||
log.Debugf("OnRemoteCandidate from peer %s -> %s", conn.config.Key, candidate.String())
|
||||
go func() {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
if conn.agent == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := conn.agent.AddRemoteCandidate(candidate)
|
||||
if err != nil {
|
||||
log.Errorf("error while handling remote candidate from peer %s", conn.config.Key)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (conn *Conn) GetKey() string {
|
||||
return conn.config.Key
|
||||
}
|
||||
144
client/internal/peer/conn_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"github.com/magiconair/properties/assert"
|
||||
"github.com/pion/ice/v2"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal/proxy"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var connConf = ConnConfig{
|
||||
Key: "LLHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
LocalKey: "RRHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
StunTurn: []*ice.URL{},
|
||||
InterfaceBlackList: nil,
|
||||
Timeout: time.Second,
|
||||
ProxyConfig: proxy.Config{},
|
||||
}
|
||||
|
||||
func TestConn_GetKey(t *testing.T) {
|
||||
conn, err := NewConn(connConf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
got := conn.GetKey()
|
||||
|
||||
assert.Equal(t, got, connConf.Key, "they should be equal")
|
||||
}
|
||||
|
||||
func TestConn_OnRemoteOffer(t *testing.T) {
|
||||
|
||||
conn, err := NewConn(connConf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
<-conn.remoteOffersCh
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
accepted := conn.OnRemoteOffer(IceCredentials{
|
||||
UFrag: "test",
|
||||
Pwd: "test",
|
||||
})
|
||||
if accepted {
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestConn_OnRemoteAnswer(t *testing.T) {
|
||||
|
||||
conn, err := NewConn(connConf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
<-conn.remoteAnswerCh
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
accepted := conn.OnRemoteAnswer(IceCredentials{
|
||||
UFrag: "test",
|
||||
Pwd: "test",
|
||||
})
|
||||
if accepted {
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
func TestConn_Status(t *testing.T) {
|
||||
|
||||
conn, err := NewConn(connConf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tables := []struct {
|
||||
name string
|
||||
status ConnStatus
|
||||
want ConnStatus
|
||||
}{
|
||||
{"StatusConnected", StatusConnected, StatusConnected},
|
||||
{"StatusDisconnected", StatusDisconnected, StatusDisconnected},
|
||||
{"StatusConnecting", StatusConnecting, StatusConnecting},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
t.Run(table.name, func(t *testing.T) {
|
||||
conn.status = table.status
|
||||
|
||||
got := conn.Status()
|
||||
assert.Equal(t, got, table.want, "they should be equal")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_Close(t *testing.T) {
|
||||
|
||||
conn, err := NewConn(connConf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
<-conn.closeCh
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
continue
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
72
client/internal/peer/error.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ConnectionTimeoutError is an error indicating that a peer Conn has been timed out
|
||||
type ConnectionTimeoutError struct {
|
||||
peer string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (e *ConnectionTimeoutError) Error() string {
|
||||
return fmt.Sprintf("connection to peer %s timed out after %s", e.peer, e.timeout.String())
|
||||
}
|
||||
|
||||
// NewConnectionTimeoutError creates a new ConnectionTimeoutError error
|
||||
func NewConnectionTimeoutError(peer string, timeout time.Duration) error {
|
||||
return &ConnectionTimeoutError{
|
||||
peer: peer,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionClosedError is an error indicating that a peer Conn has been forcefully closed
|
||||
type ConnectionClosedError struct {
|
||||
peer string
|
||||
}
|
||||
|
||||
func (e *ConnectionClosedError) Error() string {
|
||||
return fmt.Sprintf("connection to peer %s has been closed", e.peer)
|
||||
}
|
||||
|
||||
// NewConnectionClosedError creates a new ConnectionClosedError error
|
||||
func NewConnectionClosedError(peer string) error {
|
||||
return &ConnectionClosedError{
|
||||
peer: peer,
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionDisconnectedError is an error indicating that a peer Conn has ctx from the remote
|
||||
type ConnectionDisconnectedError struct {
|
||||
peer string
|
||||
}
|
||||
|
||||
func (e *ConnectionDisconnectedError) Error() string {
|
||||
return fmt.Sprintf("disconnected from peer %s", e.peer)
|
||||
}
|
||||
|
||||
// NewConnectionDisconnectedError creates a new ConnectionDisconnectedError error
|
||||
func NewConnectionDisconnectedError(peer string) error {
|
||||
return &ConnectionDisconnectedError{
|
||||
peer: peer,
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionAlreadyClosedError is an error indicating that a peer Conn has been already closed and the invocation of the Close() method has been performed over a closed connection
|
||||
type ConnectionAlreadyClosedError struct {
|
||||
peer string
|
||||
}
|
||||
|
||||
func (e *ConnectionAlreadyClosedError) Error() string {
|
||||
return fmt.Sprintf("connection to peer %s has been already closed", e.peer)
|
||||
}
|
||||
|
||||
// NewConnectionAlreadyClosed creates a new ConnectionAlreadyClosedError error
|
||||
func NewConnectionAlreadyClosed(peer string) error {
|
||||
return &ConnectionAlreadyClosedError{
|
||||
peer: peer,
|
||||
}
|
||||
}
|
||||
27
client/internal/peer/error_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewConnectionClosedError(t *testing.T) {
|
||||
err := NewConnectionClosedError("X")
|
||||
assert.Equal(t, &ConnectionClosedError{peer: "X"}, err)
|
||||
}
|
||||
|
||||
func TestNewConnectionDisconnectedError(t *testing.T) {
|
||||
err := NewConnectionDisconnectedError("X")
|
||||
assert.Equal(t, &ConnectionDisconnectedError{peer: "X"}, err)
|
||||
}
|
||||
|
||||
func TestNewConnectionTimeoutErrorC(t *testing.T) {
|
||||
err := NewConnectionTimeoutError("X", time.Second)
|
||||
assert.Equal(t, &ConnectionTimeoutError{peer: "X", timeout: time.Second}, err)
|
||||
}
|
||||
|
||||
func TestNewConnectionAlreadyClosed(t *testing.T) {
|
||||
err := NewConnectionAlreadyClosed("X")
|
||||
assert.Equal(t, &ConnectionAlreadyClosedError{peer: "X"}, err)
|
||||
}
|
||||
25
client/internal/peer/status.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package peer
|
||||
|
||||
import log "github.com/sirupsen/logrus"
|
||||
|
||||
type ConnStatus int
|
||||
|
||||
func (s ConnStatus) String() string {
|
||||
switch s {
|
||||
case StatusConnecting:
|
||||
return "StatusConnecting"
|
||||
case StatusConnected:
|
||||
return "StatusConnected"
|
||||
case StatusDisconnected:
|
||||
return "StatusDisconnected"
|
||||
default:
|
||||
log.Errorf("unknown status: %d", s)
|
||||
return "INVALID_PEER_CONNECTION_STATUS"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
StatusConnected = iota
|
||||
StatusConnecting
|
||||
StatusDisconnected
|
||||
)
|
||||
27
client/internal/peer/status_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"github.com/magiconair/properties/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConnStatus_String(t *testing.T) {
|
||||
|
||||
tables := []struct {
|
||||
name string
|
||||
status ConnStatus
|
||||
want string
|
||||
}{
|
||||
{"StatusConnected", StatusConnected, "StatusConnected"},
|
||||
{"StatusDisconnected", StatusDisconnected, "StatusDisconnected"},
|
||||
{"StatusConnecting", StatusConnecting, "StatusConnecting"},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
t.Run(table.name, func(t *testing.T) {
|
||||
got := table.status.String()
|
||||
assert.Equal(t, got, table.want, "they should be equal")
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
68
client/internal/proxy/dummy.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DummyProxy just sends pings to the RemoteKey peer and reads responses
|
||||
type DummyProxy struct {
|
||||
conn net.Conn
|
||||
remote string
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewDummyProxy(remote string) *DummyProxy {
|
||||
p := &DummyProxy{remote: remote}
|
||||
p.ctx, p.cancel = context.WithCancel(context.Background())
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *DummyProxy) Close() error {
|
||||
p.cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DummyProxy) Start(remoteConn net.Conn) error {
|
||||
p.conn = remoteConn
|
||||
go func() {
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return
|
||||
default:
|
||||
_, err := p.conn.Read(buf)
|
||||
if err != nil {
|
||||
log.Errorf("error while reading RemoteKey %s proxy %v", p.remote, err)
|
||||
return
|
||||
}
|
||||
//log.Debugf("received %s from %s", string(buf[:n]), p.remote)
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return
|
||||
default:
|
||||
_, err := p.conn.Write([]byte("hello"))
|
||||
//log.Debugf("sent ping to %s", p.remote)
|
||||
if err != nil {
|
||||
log.Errorf("error while writing to RemoteKey %s proxy %v", p.remote, err)
|
||||
return
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
25
client/internal/proxy/proxy.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DefaultWgKeepAlive = 25 * time.Second
|
||||
|
||||
type Config struct {
|
||||
WgListenAddr string
|
||||
RemoteKey string
|
||||
WgInterface iface.WGIface
|
||||
AllowedIps string
|
||||
PreSharedKey *wgtypes.Key
|
||||
}
|
||||
|
||||
type Proxy interface {
|
||||
io.Closer
|
||||
// Start creates a local remoteConn and starts proxying data from/to remoteConn
|
||||
Start(remoteConn net.Conn) error
|
||||
}
|
||||
124
client/internal/proxy/wireguard.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net"
|
||||
)
|
||||
|
||||
// WireguardProxy proxies
|
||||
type WireguardProxy struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
config Config
|
||||
|
||||
remoteConn net.Conn
|
||||
localConn net.Conn
|
||||
}
|
||||
|
||||
func NewWireguardProxy(config Config) *WireguardProxy {
|
||||
p := &WireguardProxy{config: config}
|
||||
p.ctx, p.cancel = context.WithCancel(context.Background())
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *WireguardProxy) updateEndpoint() error {
|
||||
udpAddr, err := net.ResolveUDPAddr(p.localConn.LocalAddr().Network(), p.localConn.LocalAddr().String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// add local proxy connection as a Wireguard peer
|
||||
err = p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive,
|
||||
udpAddr, p.config.PreSharedKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *WireguardProxy) Start(remoteConn net.Conn) error {
|
||||
p.remoteConn = remoteConn
|
||||
|
||||
var err error
|
||||
p.localConn, err = net.Dial("udp", p.config.WgListenAddr)
|
||||
if err != nil {
|
||||
log.Errorf("failed dialing to local Wireguard port %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.updateEndpoint()
|
||||
if err != nil {
|
||||
log.Errorf("error while updating Wireguard peer endpoint [%s] %v", p.config.RemoteKey, err)
|
||||
return err
|
||||
}
|
||||
|
||||
go p.proxyToRemote()
|
||||
go p.proxyToLocal()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *WireguardProxy) Close() error {
|
||||
p.cancel()
|
||||
if c := p.localConn; c != nil {
|
||||
err := p.localConn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := p.config.WgInterface.RemovePeer(p.config.RemoteKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// proxyToRemote proxies everything from Wireguard to the RemoteKey peer
|
||||
// blocks
|
||||
func (p *WireguardProxy) proxyToRemote() {
|
||||
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
log.Debugf("stopped proxying to remote peer %s due to closed connection", p.config.RemoteKey)
|
||||
return
|
||||
default:
|
||||
n, err := p.localConn.Read(buf)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = p.remoteConn.Write(buf[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// proxyToLocal proxies everything from the RemoteKey peer to local Wireguard
|
||||
// blocks
|
||||
func (p *WireguardProxy) proxyToLocal() {
|
||||
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
log.Debugf("stopped proxying from remote peer %s due to closed connection", p.config.RemoteKey)
|
||||
return
|
||||
default:
|
||||
n, err := p.remoteConn.Read(buf)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = p.localConn.Write(buf[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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>
|
||||
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
|
||||
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 |
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
|
||||
}
|
||||
114
cmd/init.go
@@ -1,114 +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()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
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.
|
||||
96
docs/self-hosting.md
Normal file
@@ -0,0 +1,96 @@
|
||||
### Self-hosting
|
||||
Wiretrustee is an open-source platform that can be self-hosted on your servers.
|
||||
|
||||
It relies on components developed by Wiretrustee Authors [Management Service](https://github.com/wiretrustee/wiretrustee/tree/main/management), [Management UI Dashboard](https://github.com/wiretrustee/wiretrustee-dashboard), [Signal Service](https://github.com/wiretrustee/wiretrustee/tree/main/signal),
|
||||
a 3rd party open-source STUN/TURN service [Coturn](https://github.com/coturn/coturn) and a 3rd party service [Auth0](https://auth0.com/).
|
||||
|
||||
All the components can be self-hosted except for the Auth0 service.
|
||||
We chose Auth0 to "outsource" the user management part of the platform because we believe that implementing a proper user auth requires significant amount of time to make it right.
|
||||
We focused on connectivity instead.
|
||||
|
||||
If you would like to learn more about the architecture please refer to the [Wiretrustee Architecture section](architecture.md).
|
||||
|
||||
### Step-by-step video guide on YouTube:
|
||||
|
||||
[](https://youtu.be/Ofpgx5WhT0k "Wiretrustee Self-Hosting Guide")
|
||||
|
||||
### Requirements
|
||||
|
||||
- Virtual machine offered by any cloud provider (e.g., AWS, DigitalOcean, Hetzner, Google Cloud, Azure ...).
|
||||
- Any Linux OS.
|
||||
- Docker Compose installed (see [Install Docker Compose](https://docs.docker.com/compose/install/)).
|
||||
- Domain name pointing to the public IP address of your server.
|
||||
- Open ports ```443, 33071, 33073, 10000, 3478``` (Dashboard, Management HTTP API, Management gRpc API, Signal gRpc, Coturn STUN/TURN respectively) on your server.
|
||||
- Maybe a cup of coffee or tea :)
|
||||
|
||||
### Step-by-step guide
|
||||
|
||||
For this tutorial we will be using domain ```test.wiretrustee.com``` which points to our Ubuntu 20.04 machine hosted at Hetzner.
|
||||
|
||||
1. Create Auth0 account at [auth0.com](https://auth0.com/).
|
||||
2. Login to your server, clone Wiretrustee repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/wiretrustee/wiretrustee.git wiretrustee/
|
||||
```
|
||||
|
||||
and switch to the ```wiretrustee/infrastructure_files/``` folder that contains docker compose file:
|
||||
|
||||
```bash
|
||||
cd wiretrustee/infrastructure_files/
|
||||
```
|
||||
3. Prepare configuration files.
|
||||
|
||||
To simplify the setup we have prepared a script to substitute required properties in the [docker-compose.yml.tmpl](../infrastructure_files/docker-compose.yml.tmpl) and [management.json.tmpl](../infrastructure_files/management.json.tmpl) files.
|
||||
|
||||
The [setup.env](../infrastructure_files/setup.env) file contains the following properties that have to be filled:
|
||||
|
||||
```bash
|
||||
# e.g. app.mydomain.com
|
||||
WIRETRUSTEE_DOMAIN=""
|
||||
# e.g. dev-24vkclam.us.auth0.com
|
||||
WIRETRUSTEE_AUTH0_DOMAIN=""
|
||||
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
|
||||
WIRETRUSTEE_AUTH0_CLIENT_ID=""
|
||||
# e.g. https://app.mydomain.com/
|
||||
WIRETRUSTEE_AUTH0_AUDIENCE=""
|
||||
# e.g. hello@mydomain.com
|
||||
WIRETRUSTEE_LETSENCRYPT_EMAIL=""
|
||||
```
|
||||
|
||||
Please follow the steps to get the values.
|
||||
|
||||
4. Configure ```WIRETRUSTEE_AUTH0_DOMAIN``` ```WIRETRUSTEE_AUTH0_CLIENT_ID``` ```WIRETRUSTEE_AUTH0_AUDIENCE``` properties.
|
||||
|
||||
* To obtain these, please use [Auth0 React SDK Guide](https://auth0.com/docs/quickstart/spa/react/01-login#configure-auth0) up until "Install the Auth0 React SDK".
|
||||
|
||||
:grey_exclamation: Use ```https://YOUR DOMAIN``` as ````Allowed Callback URLs````, ```Allowed Logout URLs```, ```Allowed Web Origins``` and ```Allowed Origins (CORS)```
|
||||
* set the variables in the ```setup.env```
|
||||
5. Configure ```WIRETRUSTEE_AUTH0_AUDIENCE``` property.
|
||||
|
||||
* Check [Auth0 Golang API Guide](https://auth0.com/docs/quickstart/backend/golang) to obtain AuthAudience.
|
||||
* set the property in the ```setup.env``` file.
|
||||
6. Configure ```WIRETRUSTEE_LETSENCRYPT_EMAIL``` property.
|
||||
|
||||
This can be any email address. [Let's Encrypt](https://letsencrypt.org/) will create an account while generating a new certificate.
|
||||
|
||||
7. Make sure all the properties set in the ```setup.env``` file and run:
|
||||
|
||||
```bash
|
||||
./configure.sh
|
||||
```
|
||||
|
||||
This will export all the properties as environment variables and generate ```docker-compose.yml``` and ```management.json``` files substituting required variables.
|
||||
|
||||
8. Run docker compose:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
9. Optionally check the logs by running:
|
||||
|
||||
```bash
|
||||
docker-compose logs signal
|
||||
docker-compose logs management
|
||||
docker-compose logs coturn
|
||||
docker-compose logs dashboard
|
||||
@@ -1,4 +1,4 @@
|
||||
package signal
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
@@ -7,31 +7,29 @@ import (
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
// As set of tools to encrypt/decrypt messages being sent through the Signal Exchange 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.
|
||||
// A set of tools to encrypt/decrypt messages being sent through the Signal Exchange Service or Management Service
|
||||
// These tools use Golang crypto package (Curve25519, XSalsa20 and Poly1305 to encrypt and authenticate)
|
||||
// Wireguard keys are used for encryption
|
||||
|
||||
// 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) {
|
||||
// Encrypt encrypts a message using local Wireguard private key and remote peer's public key.
|
||||
func Encrypt(msg []byte, peerPublicKey wgtypes.Key, privateKey wgtypes.Key) ([]byte, error) {
|
||||
nonce, err := genNonce()
|
||||
if err != nil {
|
||||
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.
|
||||
func Decrypt(encryptedMsg []byte, peersPublicKey wgtypes.Key, privateKey wgtypes.Key) ([]byte, error) {
|
||||
// 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, peerPublicKey wgtypes.Key, privateKey wgtypes.Key) ([]byte, error) {
|
||||
nonce, err := genNonce()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
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
|
||||
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;
|
||||
}
|
||||
75
go.mod
@@ -1,18 +1,69 @@
|
||||
module github.com/wiretrustee/wiretrustee
|
||||
|
||||
go 1.16
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v4 v4.1.0
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/google/nftables v0.0.0-20201230142148-715e31cb3c31
|
||||
github.com/pion/ice/v2 v2.1.7
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/cenkalti/backoff/v4 v4.1.2
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
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/netns v0.0.0-20191106174202-0a2b9b5464df
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||
golang.zx2c4.com/wireguard v0.0.20201118
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b
|
||||
google.golang.org/grpc v1.32.0
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
||||
golang.zx2c4.com/wireguard/windows v0.5.1
|
||||
google.golang.org/grpc v1.43.0
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/magiconair/properties v1.8.5
|
||||
github.com/rs/xid v1.3.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||
github.com/mdlayher/genetlink v1.1.0 // indirect
|
||||
github.com/mdlayher/netlink v1.4.2 // indirect
|
||||
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/pion/dtls/v2 v2.0.12 // 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.12.3 // indirect
|
||||
github.com/pion/turn/v2 v2.0.5 // 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-20211215060638-4ddde0e984e9 // 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
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
305
iface/iface.go
@@ -1,267 +1,106 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"net"
|
||||
"time"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMTU = 1280
|
||||
DefaultMTU = 1280
|
||||
)
|
||||
|
||||
// Saves tun device object - is it required?
|
||||
var tunIface tun.Device
|
||||
// WGIface represents a interface instance
|
||||
type WGIface struct {
|
||||
Name string
|
||||
Port int
|
||||
MTU int
|
||||
Address WGAddress
|
||||
Interface NetInterface
|
||||
}
|
||||
|
||||
// Create Creates a new Wireguard interface, sets a given IP and brings it up.
|
||||
// Will reuse an existing one.
|
||||
func Create(iface string, address string) error {
|
||||
var err error
|
||||
// WGAddress Wireguard parsed address
|
||||
type WGAddress struct {
|
||||
IP net.IP
|
||||
Network *net.IPNet
|
||||
}
|
||||
|
||||
tunIface, err = tun.CreateTUN(iface, defaultMTU)
|
||||
if err != nil {
|
||||
return err
|
||||
// NetInterface represents a generic network tunnel interface
|
||||
type NetInterface interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// NewWGIface Creates a new Wireguard interface instance
|
||||
func NewWGIface(iface string, address string, mtu int) (WGIface, error) {
|
||||
wgIface := WGIface{
|
||||
Name: iface,
|
||||
MTU: mtu,
|
||||
}
|
||||
|
||||
// We need to create a wireguard-go device and listen to configuration requests
|
||||
tunDevice := device.NewDevice(tunIface, device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
|
||||
tunDevice.Up()
|
||||
tunSock, err := ipc.UAPIOpen(iface)
|
||||
wgAddress, err := parseAddress(address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uapi, err := ipc.UAPIListen(iface, tunSock)
|
||||
if err != nil {
|
||||
return err
|
||||
return wgIface, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := uapi.Accept()
|
||||
if err != nil {
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
go tunDevice.IpcHandle(conn)
|
||||
wgIface.Address = wgAddress
|
||||
|
||||
return wgIface, nil
|
||||
}
|
||||
|
||||
// Exists checks whether specified Wireguard device exists or not
|
||||
func Exists(iface string) (*bool, error) {
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer wg.Close()
|
||||
|
||||
devices, err := wg.Devices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var exists bool
|
||||
for _, d := range devices {
|
||||
if d.Name == iface {
|
||||
exists = true
|
||||
return &exists, nil
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debugln("UAPI listener started")
|
||||
|
||||
err = assignAddr(iface, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
exists = false
|
||||
return &exists, nil
|
||||
}
|
||||
|
||||
// Extends the functionality of Configure(iface string, privateKey string) by generating a new Wireguard private key
|
||||
func ConfigureWithKeyGen(iface string) (*wgtypes.Key, error) {
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
// parseAddress parse a string ("1.2.3.4/24") address to WG Address
|
||||
func parseAddress(address string) (WGAddress, error) {
|
||||
ip, network, err := net.ParseCIDR(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return WGAddress{}, err
|
||||
}
|
||||
return &key, Configure(iface, key.String())
|
||||
return WGAddress{
|
||||
IP: ip,
|
||||
Network: network,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Configures a Wireguard interface
|
||||
// The interface must exist before calling this method (e.g. call interface.Create() before)
|
||||
func Configure(iface string, privateKey string) error {
|
||||
// Closes the tunnel interface
|
||||
func (w *WGIface) Close() error {
|
||||
|
||||
log.Debugf("configuring Wireguard interface %s", iface)
|
||||
wg, err := wgctrl.New()
|
||||
err := w.Interface.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wg.Close()
|
||||
|
||||
log.Debugf("adding Wireguard private key")
|
||||
key, err := wgtypes.ParseKey(privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fwmark := 0
|
||||
cfg := wgtypes.Config{
|
||||
PrivateKey: &key,
|
||||
ReplacePeers: false,
|
||||
FirewallMark: &fwmark,
|
||||
}
|
||||
err = wg.ConfigureDevice(iface, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
if runtime.GOOS == "darwin" {
|
||||
sockPath := "/var/run/wireguard/" + w.Name + ".sock"
|
||||
if _, statErr := os.Stat(sockPath); statErr == nil {
|
||||
statErr = os.Remove(sockPath)
|
||||
if statErr != nil {
|
||||
return statErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetListenPort(iface string) (*int, error) {
|
||||
log.Debugf("getting Wireguard listen port of interface %s", iface)
|
||||
|
||||
//discover Wireguard current configuration
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer wg.Close()
|
||||
|
||||
d, err := wg.Device(iface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("got Wireguard device listen port %s, %d", iface, &d.ListenPort)
|
||||
|
||||
return &d.ListenPort, nil
|
||||
}
|
||||
|
||||
// Updates a Wireguard interface listen port
|
||||
func UpdateListenPort(iface string, newPort int) error {
|
||||
log.Debugf("updating Wireguard listen port of interface %s, new port %d", iface, newPort)
|
||||
|
||||
//discover Wireguard current configuration
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wg.Close()
|
||||
|
||||
_, err = wg.Device(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("got Wireguard device %s", iface)
|
||||
|
||||
config := wgtypes.Config{
|
||||
ListenPort: &newPort,
|
||||
ReplacePeers: false,
|
||||
}
|
||||
err = wg.ConfigureDevice(iface, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("updated Wireguard listen port of interface %s, new port %d", iface, newPort)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ifname(n string) []byte {
|
||||
b := make([]byte, 16)
|
||||
copy(b, []byte(n+"\x00"))
|
||||
return b
|
||||
}
|
||||
|
||||
// Updates existing Wireguard Peer or creates a new one if doesn't exist
|
||||
// Endpoint is optional
|
||||
func UpdatePeer(iface string, peerKey string, allowedIps string, keepAlive time.Duration, endpoint string) error {
|
||||
|
||||
log.Debugf("updating interface %s peer %s: endpoint %s ", iface, peerKey, endpoint)
|
||||
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wg.Close()
|
||||
|
||||
_, err = wg.Device(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("got Wireguard device %s", iface)
|
||||
|
||||
//parse allowed ips
|
||||
ipNet, err := netlink.ParseIPNet(allowedIps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
|
||||
peers := make([]wgtypes.PeerConfig, 0)
|
||||
peer := wgtypes.PeerConfig{
|
||||
PublicKey: peerKeyParsed,
|
||||
ReplaceAllowedIPs: true,
|
||||
AllowedIPs: []net.IPNet{*ipNet},
|
||||
PersistentKeepaliveInterval: &keepAlive,
|
||||
}
|
||||
peers = append(peers, peer)
|
||||
|
||||
config := wgtypes.Config{
|
||||
ReplacePeers: false,
|
||||
Peers: peers,
|
||||
}
|
||||
err = wg.ConfigureDevice(iface, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if endpoint != "" {
|
||||
return UpdatePeerEndpoint(iface, peerKey, endpoint)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Updates a Wireguard interface Peer with the new endpoint
|
||||
// Used when NAT hole punching was successful and an update of the remote peer endpoint is required
|
||||
func UpdatePeerEndpoint(iface string, peerKey string, newEndpoint string) error {
|
||||
|
||||
log.Debugf("updating peer %s endpoint %s ", peerKey, newEndpoint)
|
||||
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wg.Close()
|
||||
|
||||
_, err = wg.Device(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("got Wireguard device %s", iface)
|
||||
|
||||
peerAddr, err := net.ResolveUDPAddr("udp4", newEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("parsed peer endpoint [%s]", peerAddr.String())
|
||||
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
peers := make([]wgtypes.PeerConfig, 0)
|
||||
peer := wgtypes.PeerConfig{
|
||||
PublicKey: peerKeyParsed,
|
||||
ReplaceAllowedIPs: false,
|
||||
UpdateOnly: true,
|
||||
Endpoint: peerAddr,
|
||||
}
|
||||
peers = append(peers, peer)
|
||||
|
||||
config := wgtypes.Config{
|
||||
ReplacePeers: false,
|
||||
Peers: peers,
|
||||
}
|
||||
err = wg.ConfigureDevice(iface, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type wgLink struct {
|
||||
attrs *netlink.LinkAttrs
|
||||
}
|
||||
|
||||
func (w *wgLink) Attrs() *netlink.LinkAttrs {
|
||||
return w.attrs
|
||||
}
|
||||
|
||||
func (w *wgLink) Type() string {
|
||||
return "wireguard"
|
||||
}
|
||||
|
||||
@@ -2,37 +2,31 @@ package iface
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
interfacePrefix = "utun"
|
||||
)
|
||||
// Create Creates a new Wireguard interface, sets a given IP and brings it up.
|
||||
func (w *WGIface) Create() error {
|
||||
return w.CreateWithUserspace()
|
||||
}
|
||||
|
||||
// assignAddr Adds IP address to the tunnel interface and network route based on the range provided
|
||||
func assignAddr(iface string, address string) error {
|
||||
ip := strings.Split(address, "/")
|
||||
cmd := exec.Command("ifconfig", iface, "inet", address, ip[0])
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
log.Infoln("Command: %v failed with output %s and error: ", cmd.String(), out)
|
||||
return err
|
||||
}
|
||||
_, resolvedNet, err := net.ParseCIDR(address)
|
||||
err = addRoute(iface, resolvedNet)
|
||||
if err != nil {
|
||||
log.Infoln("Adding route failed with error:", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (w *WGIface) assignAddr() error {
|
||||
//mask,_ := w.Address.Network.Mask.Size()
|
||||
//
|
||||
//address := fmt.Sprintf("%s/%d",w.Address.IP.String() , mask)
|
||||
|
||||
// addRoute Adds network route based on the range provided
|
||||
func addRoute(iface string, ipNet *net.IPNet) error {
|
||||
cmd := exec.Command("route", "add", "-net", ipNet.String(), "-interface", iface)
|
||||
cmd := exec.Command("ifconfig", w.Name, "inet", w.Address.IP.String(), w.Address.IP.String())
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
log.Printf("Command: %v failed with output %s and error: ", cmd.String(), out)
|
||||
log.Infof("adding addreess command \"%v\" failed with output %s and error: ", cmd.String(), out)
|
||||
return err
|
||||
}
|
||||
|
||||
routeCmd := exec.Command("route", "add", "-net", w.Address.Network.String(), "-interface", w.Name)
|
||||
if out, err := routeCmd.CombinedOutput(); err != nil {
|
||||
log.Printf("adding route command \"%v\" failed with output %s and error: ", routeCmd.String(), out)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,33 +1,167 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
interfacePrefix = "wg"
|
||||
)
|
||||
type NativeLink struct {
|
||||
Link *netlink.Link
|
||||
}
|
||||
|
||||
// assignAddr Adds IP address to the tunnel interface
|
||||
func assignAddr(iface string, address string) error {
|
||||
attrs := netlink.NewLinkAttrs()
|
||||
attrs.Name = iface
|
||||
func WireguardModExists() bool {
|
||||
link := newWGLink("mustnotexist")
|
||||
|
||||
link := wgLink{
|
||||
attrs: &attrs,
|
||||
// We willingly try to create a device with an invalid
|
||||
// MTU here as the validation of the MTU will be performed after
|
||||
// the validation of the link kind and hence allows us to check
|
||||
// for the existance of the wireguard module without actually
|
||||
// creating a link.
|
||||
//
|
||||
// As a side-effect, this will also let the kernel lazy-load
|
||||
// the wireguard module.
|
||||
link.attrs.MTU = math.MaxInt
|
||||
|
||||
err := netlink.LinkAdd(link)
|
||||
|
||||
return errors.Is(err, syscall.EINVAL)
|
||||
}
|
||||
|
||||
// Create Creates a new Wireguard interface, sets a given IP and brings it up.
|
||||
// Will reuse an existing one.
|
||||
func (w *WGIface) Create() error {
|
||||
|
||||
if WireguardModExists() {
|
||||
log.Debug("using kernel Wireguard module")
|
||||
return w.CreateWithKernel()
|
||||
} else {
|
||||
return w.CreateWithUserspace()
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWithKernel Creates a new Wireguard interface using kernel Wireguard module.
|
||||
// Works for Linux and offers much better network performance
|
||||
func (w *WGIface) CreateWithKernel() error {
|
||||
|
||||
link := newWGLink(w.Name)
|
||||
|
||||
// check if interface exists
|
||||
l, err := netlink.LinkByName(w.Name)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case netlink.LinkNotFoundError:
|
||||
break
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("adding address %s to interface: %s", address, iface)
|
||||
addr, _ := netlink.ParseAddr(address)
|
||||
err := netlink.AddrAdd(&link, addr)
|
||||
// remove if interface exists
|
||||
if l != nil {
|
||||
err = netlink.LinkDel(link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("adding device: %s", w.Name)
|
||||
err = netlink.LinkAdd(link)
|
||||
if os.IsExist(err) {
|
||||
log.Infof("interface %s already has the address: %s", iface, address)
|
||||
log.Infof("interface %s already exists. Will reuse.", w.Name)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Interface = link
|
||||
|
||||
err = w.assignAddr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// todo do a discovery
|
||||
log.Debugf("setting MTU: %d interface: %s", w.MTU, w.Name)
|
||||
err = netlink.LinkSetMTU(link, w.MTU)
|
||||
if err != nil {
|
||||
log.Errorf("error setting MTU on interface: %s", w.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("bringing up interface: %s", w.Name)
|
||||
err = netlink.LinkSetUp(link)
|
||||
if err != nil {
|
||||
log.Errorf("error bringing up interface: %s", w.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// assignAddr Adds IP address to the tunnel interface
|
||||
func (w *WGIface) assignAddr() error {
|
||||
|
||||
mask, _ := w.Address.Network.Mask.Size()
|
||||
address := fmt.Sprintf("%s/%d", w.Address.IP.String(), mask)
|
||||
|
||||
link := newWGLink(w.Name)
|
||||
|
||||
//delete existing addresses
|
||||
list, err := netlink.AddrList(link, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(list) > 0 {
|
||||
for _, a := range list {
|
||||
err = netlink.AddrDel(link, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("adding address %s to interface: %s", address, w.Name)
|
||||
addr, _ := netlink.ParseAddr(address)
|
||||
err = netlink.AddrAdd(link, addr)
|
||||
if os.IsExist(err) {
|
||||
log.Infof("interface %s already has the address: %s", w.Name, address)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
// On linux, the link must be brought up
|
||||
err = netlink.LinkSetUp(&link)
|
||||
err = netlink.LinkSetUp(link)
|
||||
return err
|
||||
}
|
||||
|
||||
type wgLink struct {
|
||||
attrs *netlink.LinkAttrs
|
||||
}
|
||||
|
||||
func newWGLink(name string) *wgLink {
|
||||
attrs := netlink.NewLinkAttrs()
|
||||
attrs.Name = name
|
||||
|
||||
return &wgLink{
|
||||
attrs: &attrs,
|
||||
}
|
||||
}
|
||||
|
||||
// Attrs returns the Wireguard's default attributes
|
||||
func (l *wgLink) Attrs() *netlink.LinkAttrs {
|
||||
return l.attrs
|
||||
}
|
||||
|
||||
// Type returns the interface type
|
||||
func (l *wgLink) Type() string {
|
||||
return "wireguard"
|
||||
}
|
||||
|
||||
// Close deletes the link interface
|
||||
func (l *wgLink) Close() error {
|
||||
return netlink.LinkDel(l)
|
||||
}
|
||||
|
||||
355
iface/iface_test.go
Normal file
@@ -0,0 +1,355 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// keep darwin compability
|
||||
const (
|
||||
WgIntNumber = 2000
|
||||
)
|
||||
|
||||
var (
|
||||
key string
|
||||
peerPubKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
privateKey, _ := wgtypes.GeneratePrivateKey()
|
||||
key = privateKey.String()
|
||||
peerPrivateKey, _ := wgtypes.GeneratePrivateKey()
|
||||
peerPubKey = peerPrivateKey.PublicKey().String()
|
||||
}
|
||||
|
||||
//
|
||||
func Test_CreateInterface(t *testing.T) {
|
||||
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+1)
|
||||
wgIP := "10.99.99.1/32"
|
||||
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = iface.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = wg.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func Test_Close(t *testing.T) {
|
||||
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+2)
|
||||
wgIP := "10.99.99.2/32"
|
||||
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = wg.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = iface.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ConfigureInterface(t *testing.T) {
|
||||
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+3)
|
||||
wgIP := "10.99.99.5/30"
|
||||
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = iface.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
port, err := iface.GetListenPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Configure(key, *port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = wg.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
wgDevice, err := wg.Device(ifaceName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if wgDevice.PrivateKey.String() != key {
|
||||
t.Fatalf("Private keys don't match after configure: %s != %s", key, wgDevice.PrivateKey.String())
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UpdatePeer(t *testing.T) {
|
||||
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+4)
|
||||
wgIP := "10.99.99.9/30"
|
||||
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = iface.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
port, err := iface.GetListenPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Configure(key, *port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keepAlive := 15 * time.Second
|
||||
allowedIP := "10.99.99.10/32"
|
||||
endpoint, err := net.ResolveUDPAddr("udp", "127.0.0.1:9900")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.UpdatePeer(peerPubKey, allowedIP, keepAlive, endpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer, err := getPeer(ifaceName, peerPubKey, t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if peer.PersistentKeepaliveInterval != keepAlive {
|
||||
t.Fatal("configured peer with mismatched keepalive interval value")
|
||||
}
|
||||
|
||||
if peer.Endpoint.String() != endpoint.String() {
|
||||
t.Fatal("configured peer with mismatched endpoint")
|
||||
}
|
||||
|
||||
var foundAllowedIP bool
|
||||
for _, aip := range peer.AllowedIPs {
|
||||
if aip.String() == allowedIP {
|
||||
foundAllowedIP = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundAllowedIP {
|
||||
t.Fatal("configured peer with mismatched Allowed IPs")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RemovePeer(t *testing.T) {
|
||||
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+4)
|
||||
wgIP := "10.99.99.13/30"
|
||||
iface, err := NewWGIface(ifaceName, wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = iface.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
port, err := iface.GetListenPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.Configure(key, *port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keepAlive := 15 * time.Second
|
||||
allowedIP := "10.99.99.14/32"
|
||||
|
||||
err = iface.UpdatePeer(peerPubKey, allowedIP, keepAlive, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface.RemovePeer(peerPubKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = getPeer(ifaceName, peerPubKey, t)
|
||||
if err.Error() != "peer not found" {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ConnectPeers(t *testing.T) {
|
||||
peer1ifaceName := fmt.Sprintf("utun%d", WgIntNumber+400)
|
||||
peer1wgIP := "10.99.99.17/30"
|
||||
peer1Key, _ := wgtypes.GeneratePrivateKey()
|
||||
//peer1Port := WgPort + 4
|
||||
|
||||
peer2ifaceName := fmt.Sprintf("utun%d", 500)
|
||||
peer2wgIP := "10.99.99.18/30"
|
||||
peer2Key, _ := wgtypes.GeneratePrivateKey()
|
||||
//peer2Port := WgPort + 5
|
||||
|
||||
keepAlive := 1 * time.Second
|
||||
|
||||
iface1, err := NewWGIface(peer1ifaceName, peer1wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface1.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer1Port, err := iface1.GetListenPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer1endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", *peer1Port))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
iface2, err := NewWGIface(peer2ifaceName, peer2wgIP, DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface2.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer2Port, err := iface2.GetListenPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer2endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", *peer2Port))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = iface1.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = iface2.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = iface1.Configure(peer1Key.String(), *peer1Port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface2.Configure(peer2Key.String(), *peer2Port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = iface1.UpdatePeer(peer2Key.PublicKey().String(), peer2wgIP, keepAlive, peer2endpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iface2.UpdatePeer(peer1Key.PublicKey().String(), peer1wgIP, keepAlive, peer1endpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
timeout := 10 * time.Second
|
||||
timeoutChannel := time.After(timeout)
|
||||
for {
|
||||
select {
|
||||
case <-timeoutChannel:
|
||||
t.Fatalf("waiting for peer handshake timeout after %s", timeout.String())
|
||||
default:
|
||||
}
|
||||
peer, gpErr := getPeer(peer1ifaceName, peer2Key.PublicKey().String(), t)
|
||||
if gpErr != nil {
|
||||
t.Fatal(gpErr)
|
||||
}
|
||||
if !peer.LastHandshakeTime.IsZero() {
|
||||
t.Log("peers successfully handshake")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getPeer(ifaceName, peerPubKey string, t *testing.T) (wgtypes.Peer, error) {
|
||||
emptyPeer := wgtypes.Peer{}
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return emptyPeer, err
|
||||
}
|
||||
defer func() {
|
||||
err = wg.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
wgDevice, err := wg.Device(ifaceName)
|
||||
if err != nil {
|
||||
return emptyPeer, err
|
||||
}
|
||||
for _, peer := range wgDevice.Peers {
|
||||
if peer.PublicKey.String() == peerPubKey {
|
||||
return peer, nil
|
||||
}
|
||||
}
|
||||
return emptyPeer, fmt.Errorf("peer not found")
|
||||
}
|
||||
63
iface/iface_unix.go
Normal file
@@ -0,0 +1,63 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package iface
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"net"
|
||||
)
|
||||
|
||||
// CreateWithUserspace Creates a new Wireguard interface, using wireguard-go userspace implementation
|
||||
func (w *WGIface) CreateWithUserspace() error {
|
||||
|
||||
tunIface, err := tun.CreateTUN(w.Name, w.MTU)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Interface = tunIface
|
||||
|
||||
// We need to create a wireguard-go device and listen to configuration requests
|
||||
tunDevice := device.NewDevice(tunIface, conn.NewDefaultBind(), device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
|
||||
err = tunDevice.Up()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uapi, err := getUAPI(w.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
uapiConn, uapiErr := uapi.Accept()
|
||||
if uapiErr != nil {
|
||||
log.Traceln("uapi Accept failed with error: ", uapiErr)
|
||||
continue
|
||||
}
|
||||
go tunDevice.IpcHandle(uapiConn)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debugln("UAPI listener started")
|
||||
|
||||
err = w.assignAddr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getUAPI returns a Listener
|
||||
func getUAPI(iface string) (net.Listener, error) {
|
||||
tunSock, err := ipc.UAPIOpen(iface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ipc.UAPIListen(iface, tunSock)
|
||||
}
|
||||
47
iface/iface_windows.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/windows/driver"
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Create Creates a new Wireguard interface, sets a given IP and brings it up.
|
||||
func (w *WGIface) Create() error {
|
||||
|
||||
WintunStaticRequestedGUID, _ := windows.GenerateGUID()
|
||||
adapter, err := driver.CreateAdapter(w.Name, "WireGuard", &WintunStaticRequestedGUID)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error creating adapter: %w", err)
|
||||
return err
|
||||
}
|
||||
w.Interface = adapter
|
||||
luid := adapter.LUID()
|
||||
err = adapter.SetLogging(driver.AdapterLogOn)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error enabling adapter logging: %w", err)
|
||||
return err
|
||||
}
|
||||
err = adapter.SetAdapterState(driver.AdapterStateUp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, _ := luid.GUID()
|
||||
log.Debugln("device guid: ", state.String())
|
||||
return w.assignAddr(luid)
|
||||
}
|
||||
|
||||
// assignAddr Adds IP address to the tunnel interface and network route based on the range provided
|
||||
func (w *WGIface) assignAddr(luid winipcfg.LUID) error {
|
||||
|
||||
log.Debugf("adding address %s to interface: %s", w.Address.IP, w.Name)
|
||||
err := luid.SetIPAddresses([]net.IPNet{{w.Address.IP, w.Address.Network.Mask}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
6
iface/ifacename.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// +build linux windows
|
||||
|
||||
package iface
|
||||
|
||||
// WgInterfaceDefault is a default interface name of Wiretrustee
|
||||
const WgInterfaceDefault = "wt0"
|
||||
6
iface/ifacename_darwin.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// +build darwin
|
||||
|
||||
package iface
|
||||
|
||||
// WgInterfaceDefault is a default interface name of Wiretrustee
|
||||
const WgInterfaceDefault = "utun100"
|
||||
@@ -1,85 +0,0 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/expr"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netns"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// Configure routing and IP masquerading
|
||||
//todo more docs on what exactly happens here and why it is needed
|
||||
func ConfigureNAT(primaryIface string) error {
|
||||
log.Debugf("adding NAT / IP masquerading using nftables")
|
||||
ns, err := netns.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn := nftables.Conn{NetNS: int(ns)}
|
||||
|
||||
log.Debugf("flushing nftable rulesets")
|
||||
conn.FlushRuleset()
|
||||
|
||||
log.Debugf("setting up nftable rules for ip masquerading")
|
||||
|
||||
nat := conn.AddTable(&nftables.Table{
|
||||
Family: nftables.TableFamilyIPv4,
|
||||
Name: "nat",
|
||||
})
|
||||
|
||||
conn.AddChain(&nftables.Chain{
|
||||
Name: "prerouting",
|
||||
Table: nat,
|
||||
Type: nftables.ChainTypeNAT,
|
||||
Hooknum: nftables.ChainHookPrerouting,
|
||||
Priority: nftables.ChainPriorityFilter,
|
||||
})
|
||||
|
||||
post := conn.AddChain(&nftables.Chain{
|
||||
Name: "postrouting",
|
||||
Table: nat,
|
||||
Type: nftables.ChainTypeNAT,
|
||||
Hooknum: nftables.ChainHookPostrouting,
|
||||
Priority: nftables.ChainPriorityNATSource,
|
||||
})
|
||||
|
||||
conn.AddRule(&nftables.Rule{
|
||||
Table: nat,
|
||||
Chain: post,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: ifname(primaryIface),
|
||||
},
|
||||
&expr.Masq{},
|
||||
},
|
||||
})
|
||||
|
||||
if err := conn.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enables IP forwarding system property.
|
||||
// Mostly used when you setup one peer as a VPN server.
|
||||
func EnableIPForward() error {
|
||||
f := "/proc/sys/net/ipv4/ip_forward"
|
||||
|
||||
content, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if string(content) == "0\n" {
|
||||
log.Info("enabling IP Forward")
|
||||
return ioutil.WriteFile(f, []byte("1"), 0600)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
7
infrastructure_files/configure.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
unset $(grep -v '^#' ./setup.env | sed -E 's/(.*)=.*/\1/' | xargs)
|
||||
export $(grep -v '^#' ./setup.env | xargs)
|
||||
|
||||
envsubst < docker-compose.yml.tmpl > docker-compose.yml
|
||||
envsubst < management.json.tmpl > management.json
|
||||
61
infrastructure_files/docker-compose.yml.tmpl
Normal file
@@ -0,0 +1,61 @@
|
||||
version: "3"
|
||||
services:
|
||||
#UI dashboard
|
||||
dashboard:
|
||||
image: wiretrustee/dashboard:main
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
environment:
|
||||
- AUTH0_DOMAIN=$WIRETRUSTEE_AUTH0_DOMAIN
|
||||
- AUTH0_CLIENT_ID=$WIRETRUSTEE_AUTH0_CLIENT_ID
|
||||
- AUTH0_AUDIENCE=$WIRETRUSTEE_AUTH0_AUDIENCE
|
||||
- WIRETRUSTEE_MGMT_API_ENDPOINT=https://$WIRETRUSTEE_DOMAIN:33071
|
||||
- NGINX_SSL_PORT=443
|
||||
- LETSENCRYPT_DOMAIN=$WIRETRUSTEE_DOMAIN
|
||||
- LETSENCRYPT_EMAIL=$WIRETRUSTEE_LETSENCRYPT_EMAIL
|
||||
volumes:
|
||||
- /var/lib/wiretrustee/dashboard/letsencrypt:/etc/letsencrypt/
|
||||
# Signal
|
||||
signal:
|
||||
image: wiretrustee/signal:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- wiretrustee-signal:/var/lib/wiretrustee
|
||||
# - /var/log/wiretrustee/signal.log:/var/log/wiretrustee/signal.log
|
||||
ports:
|
||||
- 10000:10000
|
||||
# # port and command for Let's Encrypt validation
|
||||
# - 443:443
|
||||
# command: ["--letsencrypt-domain", "$WIRETRUSTEE_DOMAIN", "--log-file", "console"]
|
||||
# Management
|
||||
management:
|
||||
image: wiretrustee/management:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- dashboard
|
||||
volumes:
|
||||
- wiretrustee-mgmt:/var/lib/wiretrustee
|
||||
- /var/lib/wiretrustee/dashboard/letsencrypt:/etc/letsencrypt:ro
|
||||
- ./management.json:/etc/wiretrustee/management.json
|
||||
# - /var/log/wiretrustee/management.log:/var/log/wiretrustee/management.log
|
||||
ports:
|
||||
- 33073:33073 #gRPC port
|
||||
- 33071:33071 #HTTP port
|
||||
# # port and command for Let's Encrypt validation
|
||||
# - 443:443
|
||||
# command: ["--letsencrypt-domain", "$WIRETRUSTEE_DOMAIN", "--log-file", "console"]
|
||||
# Coturn
|
||||
coturn:
|
||||
image: coturn/coturn
|
||||
restart: unless-stopped
|
||||
domainname: <YOUR DOMAIN>
|
||||
volumes:
|
||||
- ./turnserver.conf:/etc/turnserver.conf:ro
|
||||
# - ./privkey.pem:/etc/coturn/private/privkey.pem:ro
|
||||
# - ./cert.pem:/etc/coturn/certs/cert.pem:ro
|
||||
network_mode: host
|
||||
volumes:
|
||||
wiretrustee-mgmt:
|
||||
wiretrustee-signal:
|
||||
49
infrastructure_files/management.json.tmpl
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"Stuns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "stun:$WIRETRUSTEE_DOMAIN:3478",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
}
|
||||
],
|
||||
"TURNConfig": {
|
||||
"Turns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "turn:$WIRETRUSTEE_DOMAIN:3478",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
}
|
||||
],
|
||||
"CredentialsTTL": "12h",
|
||||
"Secret": "secret",
|
||||
"TimeBasedCredentials": false
|
||||
},
|
||||
"Signal": {
|
||||
"Proto": "http",
|
||||
"URI": "$WIRETRUSTEE_DOMAIN:10000",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
},
|
||||
"Datadir": "",
|
||||
"HttpConfig": {
|
||||
"LetsEncryptDomain": "",
|
||||
"CertFile":"/etc/letsencrypt/live/$WIRETRUSTEE_DOMAIN/fullchain.pem",
|
||||
"CertKey":"/etc/letsencrypt/live/$WIRETRUSTEE_DOMAIN/privkey.pem",
|
||||
"Address": "0.0.0.0:33071",
|
||||
"AuthIssuer": "https://$WIRETRUSTEE_AUTH0_DOMAIN/",
|
||||
"AuthAudience": "$WIRETRUSTEE_AUTH0_AUDIENCE",
|
||||
"AuthKeysLocation": "https://$WIRETRUSTEE_AUTH0_DOMAIN/.well-known/jwks.json"
|
||||
},
|
||||
"IdpManagerConfig": {
|
||||
"Manager": "none",
|
||||
"Auth0ClientCredentials": {
|
||||
"Audience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
|
||||
"AuthIssuer": "<PASTE YOUR AUTH0 Auth Issuer HERE>",
|
||||
"ClientId": "<PASTE YOUR AUTH0 Application Client ID HERE>",
|
||||
"ClientSecret": "<PASTE YOUR AUTH0 Application Client Secret HERE>",
|
||||
"GrantType": "client_credentials"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
infrastructure_files/setup.env
Normal file
@@ -0,0 +1,10 @@
|
||||
# e.g. app.mydomain.com
|
||||
WIRETRUSTEE_DOMAIN=""
|
||||
# e.g. dev-24vkclam.us.auth0.com
|
||||
WIRETRUSTEE_AUTH0_DOMAIN=""
|
||||
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
|
||||
WIRETRUSTEE_AUTH0_CLIENT_ID=""
|
||||
# e.g. https://app.mydomain.com/
|
||||
WIRETRUSTEE_AUTH0_AUDIENCE=""
|
||||
# e.g. hello@mydomain.com
|
||||
WIRETRUSTEE_LETSENCRYPT_EMAIL=""
|
||||
724
infrastructure_files/turnserver.conf
Normal file
@@ -0,0 +1,724 @@
|
||||
# Coturn TURN SERVER configuration file
|
||||
#
|
||||
# Boolean values note: where a boolean value is supposed to be used,
|
||||
# you can use '0', 'off', 'no', 'false', or 'f' as 'false,
|
||||
# and you can use '1', 'on', 'yes', 'true', or 't' as 'true'
|
||||
# If the value is missing, then it means 'true' by default.
|
||||
#
|
||||
|
||||
# Listener interface device (optional, Linux only).
|
||||
# NOT RECOMMENDED.
|
||||
#
|
||||
#listening-device=eth0
|
||||
|
||||
# TURN listener port for UDP and TCP (Default: 3478).
|
||||
# Note: actually, TLS & DTLS sessions can connect to the
|
||||
# "plain" TCP & UDP port(s), too - if allowed by configuration.
|
||||
#
|
||||
listening-port=3478
|
||||
|
||||
# TURN listener port for TLS (Default: 5349).
|
||||
# Note: actually, "plain" TCP & UDP sessions can connect to the TLS & DTLS
|
||||
# port(s), too - if allowed by configuration. The TURN server
|
||||
# "automatically" recognizes the type of traffic. Actually, two listening
|
||||
# endpoints (the "plain" one and the "tls" one) are equivalent in terms of
|
||||
# functionality; but Coturn keeps both endpoints to satisfy the RFC 5766 specs.
|
||||
# For secure TCP connections, Coturn currently supports SSL version 3 and
|
||||
# TLS version 1.0, 1.1 and 1.2.
|
||||
# For secure UDP connections, Coturn supports DTLS version 1.
|
||||
#
|
||||
tls-listening-port=5349
|
||||
|
||||
# Alternative listening port for UDP and TCP listeners;
|
||||
# default (or zero) value means "listening port plus one".
|
||||
# This is needed for RFC 5780 support
|
||||
# (STUN extension specs, NAT behavior discovery). The TURN Server
|
||||
# supports RFC 5780 only if it is started with more than one
|
||||
# listening IP address of the same family (IPv4 or IPv6).
|
||||
# RFC 5780 is supported only by UDP protocol, other protocols
|
||||
# are listening to that endpoint only for "symmetry".
|
||||
#
|
||||
#alt-listening-port=0
|
||||
|
||||
# Alternative listening port for TLS and DTLS protocols.
|
||||
# Default (or zero) value means "TLS listening port plus one".
|
||||
#
|
||||
#alt-tls-listening-port=0
|
||||
|
||||
# Some network setups will require using a TCP reverse proxy in front
|
||||
# of the STUN server. If the proxy port option is set a single listener
|
||||
# is started on the given port that accepts connections using the
|
||||
# haproxy proxy protocol v2.
|
||||
# (https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)
|
||||
#
|
||||
#tcp-proxy-port=5555
|
||||
|
||||
# Listener IP address of relay server. Multiple listeners can be specified.
|
||||
# If no IP(s) specified in the config file or in the command line options,
|
||||
# then all IPv4 and IPv6 system IPs will be used for listening.
|
||||
#
|
||||
#listening-ip=172.17.19.101
|
||||
#listening-ip=10.207.21.238
|
||||
#listening-ip=2607:f0d0:1002:51::4
|
||||
|
||||
# Auxiliary STUN/TURN server listening endpoint.
|
||||
# Aux servers have almost full TURN and STUN functionality.
|
||||
# The (minor) limitations are:
|
||||
#
|
||||
# 1) Auxiliary servers do not have alternative ports and
|
||||
# they do not support STUN RFC 5780 functionality (CHANGE REQUEST).
|
||||
#
|
||||
# 2) Auxiliary servers also are never returning ALTERNATIVE-SERVER reply.
|
||||
#
|
||||
# Valid formats are 1.2.3.4:5555 for IPv4 and [1:2::3:4]:5555 for IPv6.
|
||||
#
|
||||
# There may be multiple aux-server options, each will be used for listening
|
||||
# to client requests.
|
||||
#
|
||||
#aux-server=172.17.19.110:33478
|
||||
#aux-server=[2607:f0d0:1002:51::4]:33478
|
||||
|
||||
# (recommended for older Linuxes only)
|
||||
# Automatically balance UDP traffic over auxiliary servers (if configured).
|
||||
# The load balancing is using the ALTERNATE-SERVER mechanism.
|
||||
# The TURN client must support 300 ALTERNATE-SERVER response for this
|
||||
# functionality.
|
||||
#
|
||||
#udp-self-balance
|
||||
|
||||
# Relay interface device for relay sockets (optional, Linux only).
|
||||
# NOT RECOMMENDED.
|
||||
#
|
||||
#relay-device=eth1
|
||||
|
||||
# Relay address (the local IP address that will be used to relay the
|
||||
# packets to the peer).
|
||||
# Multiple relay addresses may be used.
|
||||
# The same IP(s) can be used as both listening IP(s) and relay IP(s).
|
||||
#
|
||||
# If no relay IP(s) specified, then the turnserver will apply the default
|
||||
# policy: it will decide itself which relay addresses to be used, and it
|
||||
# will always be using the client socket IP address as the relay IP address
|
||||
# of the TURN session (if the requested relay address family is the same
|
||||
# as the family of the client socket).
|
||||
#
|
||||
#relay-ip=172.17.19.105
|
||||
#relay-ip=2607:f0d0:1002:51::5
|
||||
|
||||
# For Amazon EC2 users:
|
||||
#
|
||||
# TURN Server public/private address mapping, if the server is behind NAT.
|
||||
# In that situation, if a -X is used in form "-X <ip>" then that ip will be reported
|
||||
# as relay IP address of all allocations. This scenario works only in a simple case
|
||||
# when one single relay address is be used, and no RFC5780 functionality is required.
|
||||
# That single relay address must be mapped by NAT to the 'external' IP.
|
||||
# The "external-ip" value, if not empty, is returned in XOR-RELAYED-ADDRESS field.
|
||||
# For that 'external' IP, NAT must forward ports directly (relayed port 12345
|
||||
# must be always mapped to the same 'external' port 12345).
|
||||
#
|
||||
# In more complex case when more than one IP address is involved,
|
||||
# that option must be used several times, each entry must
|
||||
# have form "-X <public-ip/private-ip>", to map all involved addresses.
|
||||
# RFC5780 NAT discovery STUN functionality will work correctly,
|
||||
# if the addresses are mapped properly, even when the TURN server itself
|
||||
# is behind A NAT.
|
||||
#
|
||||
# By default, this value is empty, and no address mapping is used.
|
||||
#
|
||||
# external-ip=193.224.22.37
|
||||
#
|
||||
#OR:
|
||||
#
|
||||
#external-ip=60.70.80.91/172.17.19.101
|
||||
#external-ip=60.70.80.92/172.17.19.102
|
||||
|
||||
|
||||
# Number of the relay threads to handle the established connections
|
||||
# (in addition to authentication thread and the listener thread).
|
||||
# If explicitly set to 0 then application runs relay process in a
|
||||
# single thread, in the same thread with the listener process
|
||||
# (the authentication thread will still be a separate thread).
|
||||
#
|
||||
# If this parameter is not set, then the default OS-dependent
|
||||
# thread pattern algorithm will be employed. Usually the default
|
||||
# algorithm is optimal, so you have to change this option
|
||||
# if you want to make some fine tweaks.
|
||||
#
|
||||
# In the older systems (Linux kernel before 3.9),
|
||||
# the number of UDP threads is always one thread per network listening
|
||||
# endpoint - including the auxiliary endpoints - unless 0 (zero) or
|
||||
# 1 (one) value is set.
|
||||
#
|
||||
#relay-threads=0
|
||||
|
||||
# Lower and upper bounds of the UDP relay endpoints:
|
||||
# (default values are 49152 and 65535)
|
||||
#
|
||||
min-port=49152
|
||||
max-port=65535
|
||||
|
||||
# Uncomment to run TURN server in 'normal' 'moderate' verbose mode.
|
||||
# By default the verbose mode is off.
|
||||
verbose
|
||||
|
||||
# Uncomment to run TURN server in 'extra' verbose mode.
|
||||
# This mode is very annoying and produces lots of output.
|
||||
# Not recommended under normal circumstances.
|
||||
#
|
||||
#Verbose
|
||||
|
||||
# Uncomment to use fingerprints in the TURN messages.
|
||||
# By default the fingerprints are off.
|
||||
#
|
||||
fingerprint
|
||||
|
||||
# Uncomment to use long-term credential mechanism.
|
||||
# By default no credentials mechanism is used (any user allowed).
|
||||
#
|
||||
lt-cred-mech
|
||||
|
||||
# This option is the opposite of lt-cred-mech.
|
||||
# (TURN Server with no-auth option allows anonymous access).
|
||||
# If neither option is defined, and no users are defined,
|
||||
# then no-auth is default. If at least one user is defined,
|
||||
# in this file, in command line or in usersdb file, then
|
||||
# lt-cred-mech is default.
|
||||
#
|
||||
#no-auth
|
||||
|
||||
# TURN REST API flag.
|
||||
# (Time Limited Long Term Credential)
|
||||
# Flag that sets a special authorization option that is based upon authentication secret.
|
||||
#
|
||||
# This feature's purpose is to support "TURN Server REST API", see
|
||||
# "TURN REST API" link in the project's page
|
||||
# https://github.com/coturn/coturn/
|
||||
#
|
||||
# This option is used with timestamp:
|
||||
#
|
||||
# usercombo -> "timestamp:userid"
|
||||
# turn user -> usercombo
|
||||
# turn password -> base64(hmac(secret key, usercombo))
|
||||
#
|
||||
# This allows TURN credentials to be accounted for a specific user id.
|
||||
# If you don't have a suitable id, then the timestamp alone can be used.
|
||||
# This option is enabled by turning on secret-based authentication.
|
||||
# The actual value of the secret is defined either by the option static-auth-secret,
|
||||
# or can be found in the turn_secret table in the database (see below).
|
||||
#
|
||||
# Read more about it:
|
||||
# - https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
|
||||
# - https://www.ietf.org/proceedings/87/slides/slides-87-behave-10.pdf
|
||||
#
|
||||
# Be aware that use-auth-secret overrides some parts of lt-cred-mech.
|
||||
# The use-auth-secret feature depends internally on lt-cred-mech, so if you set
|
||||
# this option then it automatically enables lt-cred-mech internally
|
||||
# as if you had enabled both.
|
||||
#
|
||||
# Note that you can use only one auth mechanism at the same time! This is because,
|
||||
# both mechanisms conduct username and password validation in different ways.
|
||||
#
|
||||
# Use either lt-cred-mech or use-auth-secret in the conf
|
||||
# to avoid any confusion.
|
||||
#
|
||||
#use-auth-secret
|
||||
|
||||
# 'Static' authentication secret value (a string) for TURN REST API only.
|
||||
# If not set, then the turn server
|
||||
# will try to use the 'dynamic' value in the turn_secret table
|
||||
# in the user database (if present). The database-stored value can be changed on-the-fly
|
||||
# by a separate program, so this is why that mode is considered 'dynamic'.
|
||||
#
|
||||
#static-auth-secret=north
|
||||
|
||||
# Server name used for
|
||||
# the oAuth authentication purposes.
|
||||
# The default value is the realm name.
|
||||
#
|
||||
# server-name=stun.wiretrustee.com
|
||||
|
||||
# Flag that allows oAuth authentication.
|
||||
#
|
||||
#oauth
|
||||
|
||||
# 'Static' user accounts for the long term credentials mechanism, only.
|
||||
# This option cannot be used with TURN REST API.
|
||||
# 'Static' user accounts are NOT dynamically checked by the turnserver process,
|
||||
# so they can NOT be changed while the turnserver is running.
|
||||
#
|
||||
#user=username1:key1
|
||||
#user=username2:key2
|
||||
# OR:
|
||||
user=username1:password1
|
||||
#user=username2:password2
|
||||
#
|
||||
# Keys must be generated by turnadmin utility. The key value depends
|
||||
# on user name, realm, and password:
|
||||
#
|
||||
# Example:
|
||||
# $ turnadmin -k -u ninefingers -r north.gov -p youhavetoberealistic
|
||||
# Output: 0xbc807ee29df3c9ffa736523fb2c4e8ee
|
||||
# ('0x' in the beginning of the key is what differentiates the key from
|
||||
# password. If it has 0x then it is a key, otherwise it is a password).
|
||||
#
|
||||
# The corresponding user account entry in the config file will be:
|
||||
#
|
||||
#user=ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee
|
||||
# Or, equivalently, with open clear password (less secure):
|
||||
#user=ninefingers:youhavetoberealistic
|
||||
#
|
||||
|
||||
# SQLite database file name.
|
||||
#
|
||||
# The default file name is /var/db/turndb or /usr/local/var/db/turndb or
|
||||
# /var/lib/turn/turndb.
|
||||
#
|
||||
#userdb=/var/db/turndb
|
||||
|
||||
# PostgreSQL database connection string in the case that you are using PostgreSQL
|
||||
# as the user database.
|
||||
# This database can be used for the long-term credential mechanism
|
||||
# and it can store the secret value for secret-based timed authentication in TURN REST API.
|
||||
# See http://www.postgresql.org/docs/8.4/static/libpq-connect.html for 8.x PostgreSQL
|
||||
# versions connection string format, see
|
||||
# http://www.postgresql.org/docs/9.2/static/libpq-connect.html#LIBPQ-CONNSTRING
|
||||
# for 9.x and newer connection string formats.
|
||||
#
|
||||
#psql-userdb="host=<host> dbname=<database-name> user=<database-user> password=<database-user-password> connect_timeout=30"
|
||||
|
||||
# MySQL database connection string in the case that you are using MySQL
|
||||
# as the user database.
|
||||
# This database can be used for the long-term credential mechanism
|
||||
# and it can store the secret value for secret-based timed authentication in TURN REST API.
|
||||
#
|
||||
# Optional connection string parameters for the secure communications (SSL):
|
||||
# ca, capath, cert, key, cipher
|
||||
# (see http://dev.mysql.com/doc/refman/5.1/en/ssl-options.html for the
|
||||
# command options description).
|
||||
#
|
||||
# Use the string format below (space separated parameters, all optional):
|
||||
#
|
||||
# mysql-userdb="host=mysql dbname=coturn user=coturn password=CHANGE_ME port=3306 connect_timeout=10 read_timeout=10"
|
||||
|
||||
# If you want to use an encrypted password in the MySQL connection string,
|
||||
# then set the MySQL password encryption secret key file with this option.
|
||||
#
|
||||
# Warning: If this option is set, then the mysql password must be set in "mysql-userdb" in an encrypted format!
|
||||
# If you want to use a cleartext password then do not set this option!
|
||||
#
|
||||
# This is the file path for the aes encrypted secret key used for password encryption.
|
||||
#
|
||||
#secret-key-file=/path/
|
||||
|
||||
# MongoDB database connection string in the case that you are using MongoDB
|
||||
# as the user database.
|
||||
# This database can be used for long-term credential mechanism
|
||||
# and it can store the secret value for secret-based timed authentication in TURN REST API.
|
||||
# Use the string format described at http://hergert.me/docs/mongo-c-driver/mongoc_uri.html
|
||||
#
|
||||
#mongo-userdb="mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]"
|
||||
|
||||
# Redis database connection string in the case that you are using Redis
|
||||
# as the user database.
|
||||
# This database can be used for long-term credential mechanism
|
||||
# and it can store the secret value for secret-based timed authentication in TURN REST API.
|
||||
# Use the string format below (space separated parameters, all optional):
|
||||
#
|
||||
#redis-userdb="ip=<ip-address> dbname=<database-number> password=<database-user-password> port=<port> connect_timeout=<seconds>"
|
||||
|
||||
# Redis status and statistics database connection string, if used (default - empty, no Redis stats DB used).
|
||||
# This database keeps allocations status information, and it can be also used for publishing
|
||||
# and delivering traffic and allocation event notifications.
|
||||
# The connection string has the same parameters as redis-userdb connection string.
|
||||
# Use the string format below (space separated parameters, all optional):
|
||||
#
|
||||
#redis-statsdb="ip=<ip-address> dbname=<database-number> password=<database-user-password> port=<port> connect_timeout=<seconds>"
|
||||
|
||||
# The default realm to be used for the users when no explicit
|
||||
# origin/realm relationship is found in the database, or if the TURN
|
||||
# server is not using any database (just the commands-line settings
|
||||
# and the userdb file). Must be used with long-term credentials
|
||||
# mechanism or with TURN REST API.
|
||||
#
|
||||
# Note: If the default realm is not specified, then realm falls back to the host domain name.
|
||||
# If the domain name string is empty, or set to '(None)', then it is initialized as an empty string.
|
||||
#
|
||||
realm=wiretrustee.com
|
||||
# This flag sets the origin consistency
|
||||
# check. Across the session, all requests must have the same
|
||||
# main ORIGIN attribute value (if the ORIGIN was
|
||||
# initially used by the session).
|
||||
#
|
||||
#check-origin-consistency
|
||||
|
||||
# Per-user allocation quota.
|
||||
# default value is 0 (no quota, unlimited number of sessions per user).
|
||||
# This option can also be set through the database, for a particular realm.
|
||||
#
|
||||
#user-quota=0
|
||||
|
||||
# Total allocation quota.
|
||||
# default value is 0 (no quota).
|
||||
# This option can also be set through the database, for a particular realm.
|
||||
#
|
||||
#total-quota=0
|
||||
|
||||
# Max bytes-per-second bandwidth a TURN session is allowed to handle
|
||||
# (input and output network streams are treated separately). Anything above
|
||||
# that limit will be dropped or temporarily suppressed (within
|
||||
# the available buffer limits).
|
||||
# This option can also be set through the database, for a particular realm.
|
||||
#
|
||||
#max-bps=0
|
||||
|
||||
#
|
||||
# Maximum server capacity.
|
||||
# Total bytes-per-second bandwidth the TURN server is allowed to allocate
|
||||
# for the sessions, combined (input and output network streams are treated separately).
|
||||
#
|
||||
# bps-capacity=0
|
||||
|
||||
# Uncomment if no UDP client listener is desired.
|
||||
# By default UDP client listener is always started.
|
||||
#
|
||||
#no-udp
|
||||
|
||||
# Uncomment if no TCP client listener is desired.
|
||||
# By default TCP client listener is always started.
|
||||
#
|
||||
#no-tcp
|
||||
|
||||
# Uncomment if no TLS client listener is desired.
|
||||
# By default TLS client listener is always started.
|
||||
#
|
||||
#no-tls
|
||||
|
||||
# Uncomment if no DTLS client listener is desired.
|
||||
# By default DTLS client listener is always started.
|
||||
#
|
||||
#no-dtls
|
||||
|
||||
# Uncomment if no UDP relay endpoints are allowed.
|
||||
# By default UDP relay endpoints are enabled (like in RFC 5766).
|
||||
#
|
||||
#no-udp-relay
|
||||
|
||||
# Uncomment if no TCP relay endpoints are allowed.
|
||||
# By default TCP relay endpoints are enabled (like in RFC 6062).
|
||||
#
|
||||
#no-tcp-relay
|
||||
|
||||
# Uncomment if extra security is desired,
|
||||
# with nonce value having a limited lifetime.
|
||||
# The nonce value is unique for a session.
|
||||
# Set this option to limit the nonce lifetime.
|
||||
# Set it to 0 for unlimited lifetime.
|
||||
# It defaults to 600 secs (10 min) if no value is provided. After that delay,
|
||||
# the client will get 438 error and will have to re-authenticate itself.
|
||||
#
|
||||
#stale-nonce=600
|
||||
|
||||
# Uncomment if you want to set the maximum allocation
|
||||
# time before it has to be refreshed.
|
||||
# Default is 3600s.
|
||||
#
|
||||
#max-allocate-lifetime=3600
|
||||
|
||||
|
||||
# Uncomment to set the lifetime for the channel.
|
||||
# Default value is 600 secs (10 minutes).
|
||||
# This value MUST not be changed for production purposes.
|
||||
#
|
||||
#channel-lifetime=600
|
||||
|
||||
# Uncomment to set the permission lifetime.
|
||||
# Default to 300 secs (5 minutes).
|
||||
# In production this value MUST not be changed,
|
||||
# however it can be useful for test purposes.
|
||||
#
|
||||
#permission-lifetime=300
|
||||
|
||||
# Certificate file.
|
||||
# Use an absolute path or path relative to the
|
||||
# configuration file.
|
||||
# Use PEM file format.
|
||||
#
|
||||
cert=/etc/coturn/certs/cert.pem
|
||||
|
||||
# Private key file.
|
||||
# Use an absolute path or path relative to the
|
||||
# configuration file.
|
||||
# Use PEM file format.
|
||||
#
|
||||
pkey=/etc/coturn/private/privkey.pem
|
||||
|
||||
# Private key file password, if it is in encoded format.
|
||||
# This option has no default value.
|
||||
#
|
||||
#pkey-pwd=...
|
||||
|
||||
# Allowed OpenSSL cipher list for TLS/DTLS connections.
|
||||
# Default value is "DEFAULT".
|
||||
#
|
||||
#cipher-list="DEFAULT"
|
||||
|
||||
# CA file in OpenSSL format.
|
||||
# Forces TURN server to verify the client SSL certificates.
|
||||
# By default this is not set: there is no default value and the client
|
||||
# certificate is not checked.
|
||||
#
|
||||
# Example:
|
||||
#CA-file=/etc/ssh/id_rsa.cert
|
||||
|
||||
# Curve name for EC ciphers, if supported by OpenSSL
|
||||
# library (TLS and DTLS). The default value is prime256v1,
|
||||
# if pre-OpenSSL 1.0.2 is used. With OpenSSL 1.0.2+,
|
||||
# an optimal curve will be automatically calculated, if not defined
|
||||
# by this option.
|
||||
#
|
||||
#ec-curve-name=prime256v1
|
||||
|
||||
# Use 566 bits predefined DH TLS key. Default size of the key is 2066.
|
||||
#
|
||||
#dh566
|
||||
|
||||
# Use 1066 bits predefined DH TLS key. Default size of the key is 2066.
|
||||
#
|
||||
#dh1066
|
||||
|
||||
# Use custom DH TLS key, stored in PEM format in the file.
|
||||
# Flags --dh566 and --dh2066 are ignored when the DH key is taken from a file.
|
||||
#
|
||||
#dh-file=<DH-PEM-file-name>
|
||||
|
||||
# Flag to prevent stdout log messages.
|
||||
# By default, all log messages go to both stdout and to
|
||||
# the configured log file. With this option everything will
|
||||
# go to the configured log only (unless the log file itself is stdout).
|
||||
#
|
||||
#no-stdout-log
|
||||
|
||||
# Option to set the log file name.
|
||||
# By default, the turnserver tries to open a log file in
|
||||
# /var/log, /var/tmp, /tmp and the current directory
|
||||
# (Whichever file open operation succeeds first will be used).
|
||||
# With this option you can set the definite log file name.
|
||||
# The special names are "stdout" and "-" - they will force everything
|
||||
# to the stdout. Also, the "syslog" name will force everything to
|
||||
# the system log (syslog).
|
||||
# In the runtime, the logfile can be reset with the SIGHUP signal
|
||||
# to the turnserver process.
|
||||
#
|
||||
log-file=stdout
|
||||
|
||||
# Option to redirect all log output into system log (syslog).
|
||||
#
|
||||
# syslog
|
||||
|
||||
# This flag means that no log file rollover will be used, and the log file
|
||||
# name will be constructed as-is, without PID and date appendage.
|
||||
# This option can be used, for example, together with the logrotate tool.
|
||||
#
|
||||
#simple-log
|
||||
|
||||
# Option to set the "redirection" mode. The value of this option
|
||||
# will be the address of the alternate server for UDP & TCP service in the form of
|
||||
# <ip>[:<port>]. The server will send this value in the attribute
|
||||
# ALTERNATE-SERVER, with error 300, on ALLOCATE request, to the client.
|
||||
# Client will receive only values with the same address family
|
||||
# as the client network endpoint address family.
|
||||
# See RFC 5389 and RFC 5766 for the description of ALTERNATE-SERVER functionality.
|
||||
# The client must use the obtained value for subsequent TURN communications.
|
||||
# If more than one --alternate-server option is provided, then the functionality
|
||||
# can be more accurately described as "load-balancing" than a mere "redirection".
|
||||
# If the port number is omitted, then the default port
|
||||
# number 3478 for the UDP/TCP protocols will be used.
|
||||
# Colon (:) characters in IPv6 addresses may conflict with the syntax of
|
||||
# the option. To alleviate this conflict, literal IPv6 addresses are enclosed
|
||||
# in square brackets in such resource identifiers, for example:
|
||||
# [2001:db8:85a3:8d3:1319:8a2e:370:7348]:3478 .
|
||||
# Multiple alternate servers can be set. They will be used in the
|
||||
# round-robin manner. All servers in the pool are considered of equal weight and
|
||||
# the load will be distributed equally. For example, if you have 4 alternate servers,
|
||||
# then each server will receive 25% of ALLOCATE requests. A alternate TURN server
|
||||
# address can be used more than one time with the alternate-server option, so this
|
||||
# can emulate "weighting" of the servers.
|
||||
#
|
||||
# Examples:
|
||||
#alternate-server=1.2.3.4:5678
|
||||
#alternate-server=11.22.33.44:56789
|
||||
#alternate-server=5.6.7.8
|
||||
#alternate-server=[2001:db8:85a3:8d3:1319:8a2e:370:7348]:3478
|
||||
|
||||
# Option to set alternative server for TLS & DTLS services in form of
|
||||
# <ip>:<port>. If the port number is omitted, then the default port
|
||||
# number 5349 for the TLS/DTLS protocols will be used. See the previous
|
||||
# option for the functionality description.
|
||||
#
|
||||
# Examples:
|
||||
#tls-alternate-server=1.2.3.4:5678
|
||||
#tls-alternate-server=11.22.33.44:56789
|
||||
#tls-alternate-server=[2001:db8:85a3:8d3:1319:8a2e:370:7348]:3478
|
||||
|
||||
# Option to suppress TURN functionality, only STUN requests will be processed.
|
||||
# Run as STUN server only, all TURN requests will be ignored.
|
||||
# By default, this option is NOT set.
|
||||
#
|
||||
#stun-only
|
||||
|
||||
# Option to hide software version. Enhance security when used in production.
|
||||
# Revealing the specific software version of the agent through the
|
||||
# SOFTWARE attribute might allow them to become more vulnerable to
|
||||
# attacks against software that is known to contain security holes.
|
||||
# Implementers SHOULD make usage of the SOFTWARE attribute a
|
||||
# configurable option (https://tools.ietf.org/html/rfc5389#section-16.1.2)
|
||||
#
|
||||
no-software-attribute
|
||||
|
||||
# Option to suppress STUN functionality, only TURN requests will be processed.
|
||||
# Run as TURN server only, all STUN requests will be ignored.
|
||||
# By default, this option is NOT set.
|
||||
#
|
||||
#no-stun
|
||||
|
||||
# This is the timestamp/username separator symbol (character) in TURN REST API.
|
||||
# The default value is ':'.
|
||||
# rest-api-separator=:
|
||||
|
||||
# Flag that can be used to allow peers on the loopback addresses (127.x.x.x and ::1).
|
||||
# This is an extra security measure.
|
||||
#
|
||||
# (To avoid any security issue that allowing loopback access may raise,
|
||||
# the no-loopback-peers option is replaced by allow-loopback-peers.)
|
||||
#
|
||||
# Allow it only for testing in a development environment!
|
||||
# In production it adds a possible security vulnerability, so for security reasons
|
||||
# it is not allowed using it together with empty cli-password.
|
||||
#
|
||||
#allow-loopback-peers
|
||||
|
||||
# Flag that can be used to disallow peers on well-known broadcast addresses (224.0.0.0 and above, and FFXX:*).
|
||||
# This is an extra security measure.
|
||||
#
|
||||
#no-multicast-peers
|
||||
|
||||
# Option to set the max time, in seconds, allowed for full allocation establishment.
|
||||
# Default is 60 seconds.
|
||||
#
|
||||
#max-allocate-timeout=60
|
||||
|
||||
# Option to allow or ban specific ip addresses or ranges of ip addresses.
|
||||
# If an ip address is specified as both allowed and denied, then the ip address is
|
||||
# considered to be allowed. This is useful when you wish to ban a range of ip
|
||||
# addresses, except for a few specific ips within that range.
|
||||
#
|
||||
# This can be used when you do not want users of the turn server to be able to access
|
||||
# machines reachable by the turn server, but would otherwise be unreachable from the
|
||||
# internet (e.g. when the turn server is sitting behind a NAT)
|
||||
#
|
||||
# Examples:
|
||||
# denied-peer-ip=83.166.64.0-83.166.95.255
|
||||
# allowed-peer-ip=83.166.68.45
|
||||
|
||||
# File name to store the pid of the process.
|
||||
# Default is /var/run/turnserver.pid (if superuser account is used) or
|
||||
# /var/tmp/turnserver.pid .
|
||||
#
|
||||
pidfile="/var/tmp/turnserver.pid"
|
||||
|
||||
# Require authentication of the STUN Binding request.
|
||||
# By default, the clients are allowed anonymous access to the STUN Binding functionality.
|
||||
#
|
||||
#secure-stun
|
||||
|
||||
# Mobility with ICE (MICE) specs support.
|
||||
#
|
||||
#mobility
|
||||
|
||||
# Allocate Address Family according
|
||||
# If enabled then TURN server allocates address family according the TURN
|
||||
# Client <=> Server communication address family.
|
||||
# (By default Coturn works according RFC 6156.)
|
||||
# !!Warning: Enabling this option breaks RFC6156 section-4.2 (violates use default IPv4)!!
|
||||
#
|
||||
#keep-address-family
|
||||
|
||||
|
||||
# User name to run the process. After the initialization, the turnserver process
|
||||
# will attempt to change the current user ID to that user.
|
||||
#
|
||||
#proc-user=<user-name>
|
||||
|
||||
# Group name to run the process. After the initialization, the turnserver process
|
||||
# will attempt to change the current group ID to that group.
|
||||
#
|
||||
#proc-group=<group-name>
|
||||
|
||||
# Turn OFF the CLI support.
|
||||
# By default it is always ON.
|
||||
# See also options cli-ip and cli-port.
|
||||
#
|
||||
no-cli
|
||||
|
||||
#Local system IP address to be used for CLI server endpoint. Default value
|
||||
# is 127.0.0.1.
|
||||
#
|
||||
# cli-ip=127.0.0.1
|
||||
|
||||
# CLI server port. Default is 5766.
|
||||
#
|
||||
# cli-port=5766
|
||||
|
||||
# CLI access password. Default is empty (no password).
|
||||
# For the security reasons, it is recommended that you use the encrypted
|
||||
# form of the password (see the -P command in the turnadmin utility).
|
||||
#
|
||||
# Secure form for password 'qwerty':
|
||||
#
|
||||
#cli-password=$5$79a316b350311570$81df9cfb9af7f5e5a76eada31e7097b663a0670f99a3c07ded3f1c8e59c5658a
|
||||
#
|
||||
# Or unsecure form for the same password:
|
||||
#
|
||||
# cli-password=CHANGE_ME
|
||||
|
||||
# Enable Web-admin support on https. By default it is Disabled.
|
||||
# If it is enabled it also enables a http a simple static banner page
|
||||
# with a small reminder that the admin page is available only on https.
|
||||
#
|
||||
#web-admin
|
||||
|
||||
# Local system IP address to be used for Web-admin server endpoint. Default value is 127.0.0.1.
|
||||
#
|
||||
#web-admin-ip=127.0.0.1
|
||||
|
||||
# Web-admin server port. Default is 8080.
|
||||
#
|
||||
#web-admin-port=8080
|
||||
|
||||
# Web-admin server listen on STUN/TURN worker threads
|
||||
# By default it is disabled for security resons! (Not recommended in any production environment!)
|
||||
#
|
||||
#web-admin-listen-on-workers
|
||||
|
||||
# Server relay. NON-STANDARD AND DANGEROUS OPTION.
|
||||
# Only for those applications when you want to run
|
||||
# server applications on the relay endpoints.
|
||||
# This option eliminates the IP permissions check on
|
||||
# the packets incoming to the relay endpoints.
|
||||
#
|
||||
#server-relay
|
||||
|
||||
# Maximum number of output sessions in ps CLI command.
|
||||
# This value can be changed on-the-fly in CLI. The default value is 256.
|
||||
#
|
||||
#cli-max-output-sessions
|
||||
|
||||
# Set network engine type for the process (for internal purposes).
|
||||
#
|
||||
#ne=[1|2|3]
|
||||
|
||||
# Do not allow an TLS/DTLS version of protocol
|
||||
#
|
||||
#no-tlsv1
|
||||
#no-tlsv1_1
|
||||
#no-tlsv1_2
|
||||
4
management/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM gcr.io/distroless/base
|
||||
ENTRYPOINT [ "/go/bin/wiretrustee-mgmt","management"]
|
||||
CMD ["--log-file", "console"]
|
||||
COPY wiretrustee-mgmt /go/bin/wiretrustee-mgmt
|
||||