Compare commits
234 Commits
v0.0.3
...
v0.1.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
59
.github/workflows/golang-test.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
name: Test
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.16.x]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Test
|
||||
run: GOBIN=$(which go) && sudo --preserve-env=GOROOT $GOBIN test -p 1 ./...
|
||||
|
||||
test_build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ windows, linux, darwin ]
|
||||
go-version: [1.16.x]
|
||||
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@v1
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Install modules
|
||||
run: 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
|
||||
16
.github/workflows/golangci-lint.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: golangci-lint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
|
||||
|
||||
26
.github/workflows/release.yml
vendored
@@ -30,12 +30,34 @@ 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 }}
|
||||
|
||||
-
|
||||
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 }}" }'
|
||||
8
.gitignore
vendored
@@ -1,2 +1,8 @@
|
||||
.idea
|
||||
*.iml
|
||||
*.iml
|
||||
dist/
|
||||
.env
|
||||
conf.json
|
||||
http-cmds.sh
|
||||
infrastructure_files/management.json
|
||||
infrastructure_files/docker-compose.yml
|
||||
171
.goreleaser.yaml
@@ -1,9 +1,14 @@
|
||||
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
|
||||
@@ -11,10 +16,41 @@ builds:
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
tags:
|
||||
- load_wintun_from_rsrc
|
||||
|
||||
- id: wiretrustee-mgmt
|
||||
dir: management
|
||||
env: [CGO_ENABLED=0]
|
||||
binary: wiretrustee-mgmt
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
|
||||
- id: wiretrustee-signal
|
||||
dir: signal
|
||||
env: [CGO_ENABLED=0]
|
||||
binary: wiretrustee-signal
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
archives:
|
||||
- builds:
|
||||
- wiretrustee
|
||||
nfpms:
|
||||
- maintainer: Wiretrustee <wiretrustee@wiretrustee.com>
|
||||
description: Wiretrustee project.
|
||||
homepage: https://wiretrustee.com/
|
||||
builds:
|
||||
- wiretrustee
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
@@ -28,3 +64,136 @@ nfpms:
|
||||
|
||||
scripts:
|
||||
postinstall: "release_files/post_install.sh"
|
||||
dockers:
|
||||
- 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/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 }}-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"
|
||||
|
||||
docker_manifests:
|
||||
- name_template: wiretrustee/signal:{{ .Version }}
|
||||
image_templates:
|
||||
- wiretrustee/signal:{{ .Version }}-arm64v8
|
||||
- wiretrustee/signal:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/signal:latest
|
||||
image_templates:
|
||||
- wiretrustee/signal:{{ .Version }}-arm64v8
|
||||
- wiretrustee/signal:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/management:{{ .Version }}
|
||||
image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-arm64v8
|
||||
- wiretrustee/management:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/management:latest
|
||||
image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-arm64v8
|
||||
- wiretrustee/management:{{ .Version }}-amd64
|
||||
|
||||
- name_template: wiretrustee/management:debug-latest
|
||||
image_templates:
|
||||
- wiretrustee/management:{{ .Version }}-debug-arm64v8
|
||||
- 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"
|
||||
2
AUTHORS
Normal file
@@ -0,0 +1,2 @@
|
||||
Mikhail Bragin (https://github.com/braginini)
|
||||
Maycon Santos (https://github.com/mlsmaycon)
|
||||
13
LICENSE
Normal file
@@ -0,0 +1,13 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021 Wiretrustee 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.
|
||||
204
README.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# Wiretrustee
|
||||
|
||||
A WireGuard®-based mesh network that connects your devices into a single private network.
|
||||
|
||||
**Note**: The `main` branch may be in an *unstable or even broken state* during development. For stable versions, see [releases](https://github.com/wiretrustee/wiretrustee/releases).
|
||||
|
||||
Hosted demo version:
|
||||
[https://beta.wiretrustee.com/](https://beta.wiretrustee.com/peers).
|
||||
|
||||
The number of peers is limited to 15.
|
||||
|
||||
Please don't use the hosted demonstration version for production purposes. We appreciate your usage of the demonstration version and will love to hear your feedback, please reach out via Issue or [Slack](https://wiretrustee.slack.com/).
|
||||
|
||||
[UI Dashboard Repo](https://github.com/wiretrustee/wiretrustee-dashboard)
|
||||
|
||||
### Why using Wiretrustee?
|
||||
|
||||
* Connect multiple devices to each other via a secure peer-to-peer Wireguard VPN tunnel. At home, the office, or anywhere else.
|
||||
* No need to open ports and expose public IPs on the device, routers etc.
|
||||
* Uses Kernel Wireguard module if available.
|
||||
* Automatic network change detection. When a new peer joins the network others are notified and keys are exchanged automatically.
|
||||
* Automatically reconnects in case of network failures or switches.
|
||||
* Automatic NAT traversal.
|
||||
* Relay server fallback in case of an unsuccessful peer-to-peer connection.
|
||||
* Private key never leaves your device.
|
||||
* Automatic IP address management.
|
||||
* Intuitive UI Dashboard.
|
||||
* Works on ARM devices (e.g. Raspberry Pi).
|
||||
* Open-source (including Management Service)
|
||||
|
||||
### Secure peer-to-peer VPN in minutes
|
||||

|
||||
|
||||
### A bit on Wiretrustee internals
|
||||
* Wiretrustee features a Management Service that offers peer IP management and network updates distribution (e.g. when new peer joins the network).
|
||||
* Wiretrustee uses WebRTC ICE implemented in [pion/ice library](https://github.com/pion/ice) to discover connection candidates when establishing a peer-to-peer connection between devices.
|
||||
* Peers negotiate connection through [Signal Service](signal/).
|
||||
* Signal Service uses public Wireguard keys to route messages between peers.
|
||||
Contents of the messages sent between peers through the signaling server are encrypted with Wireguard keys, making it impossible to inspect them.
|
||||
* Occasionally, the NAT-traversal is unsuccessful due to strict NATs (e.g. mobile carrier-grade NAT).
|
||||
When this occurs the system falls back to relay server (TURN), and a secure Wireguard tunnel is established via TURN server.
|
||||
[Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in Wiretrustee setups.
|
||||
|
||||
### Product Roadmap
|
||||
- [Public Roadmap](https://github.com/wiretrustee/wiretrustee/projects/2)
|
||||
- [Public Roadmap Progress Tracking](https://github.com/wiretrustee/wiretrustee/projects/1)
|
||||
|
||||
### Getting started
|
||||
|
||||
See [Docs](docs/README.md) for managed and self-hosting guides.
|
||||
|
||||
### Client Installation
|
||||
#### Linux
|
||||
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases)
|
||||
2. Download the latest release (**Switch VERSION to the latest**):
|
||||
|
||||
**Debian packages**
|
||||
```shell
|
||||
wget https://github.com/wiretrustee/wiretrustee/releases/download/v<VERSION>/wiretrustee_<VERSION>_linux_amd64.deb
|
||||
```
|
||||
3. Install the package
|
||||
```shell
|
||||
sudo dpkg -i wiretrustee_<VERSION>_linux_amd64.deb
|
||||
```
|
||||
**Fedora/Centos packages**
|
||||
```shell
|
||||
wget https://github.com/wiretrustee/wiretrustee/releases/download/v<VERSION>/wiretrustee_<VERSION>_linux_amd64.rpm
|
||||
```
|
||||
3. Install the package
|
||||
```shell
|
||||
sudo rpm -i wiretrustee_<VERSION>_linux_amd64.rpm
|
||||
```
|
||||
#### 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 login --setup-key <SETUP KEY>
|
||||
```
|
||||
For **Windows** systems:
|
||||
```shell
|
||||
.\wiretrustee.exe login --setup-key <SETUP KEY>
|
||||
```
|
||||
|
||||
Alternatively, if you are hosting your own Management Service provide `--management-url` property pointing to your Management Service:
|
||||
```shell
|
||||
sudo wiretrustee login --setup-key <SETUP KEY> --management-url https://localhost:33073
|
||||
```
|
||||
|
||||
You could also omit `--setup-key` property. In this case the tool will prompt it the key.
|
||||
|
||||
2. Start Wiretrustee:
|
||||
|
||||
For **MACOS** you will just start the service:
|
||||
````shell
|
||||
sudo wiretrustee up
|
||||
# or
|
||||
sudo wiretrustee up & # to run it in background
|
||||
````
|
||||
For **Linux** systems:
|
||||
```shell
|
||||
sudo systemctl restart wiretrustee.service
|
||||
sudo systemctl status wiretrustee.service
|
||||
```
|
||||
For **Windows** systems:
|
||||
```shell
|
||||
.\wiretrustee.exe service start
|
||||
```
|
||||
> You may need to run Powershell as Administrator
|
||||
|
||||
3. Check your IP:
|
||||
For **MACOS** you will just start the service:
|
||||
````shell
|
||||
sudo ipconfig getifaddr utun100
|
||||
````
|
||||
For **Linux** systems:
|
||||
```shell
|
||||
ip addr show wt0
|
||||
```
|
||||
For **Windows** systems:
|
||||
```shell
|
||||
netsh interface ip show config name="wt0"
|
||||
```
|
||||
|
||||
4. 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 significant amount of time to make it right. We focused on connectivity instead.
|
||||
It is worth mentioning that dependency to 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, then switch the domainname to your own SSL domain. If you don't already have an SSL certificate, you can follow [Certbot's](https://certbot.eff.org/docs/intro.html) official documentation
|
||||
to generate one from [Let’s Encrypt](https://letsencrypt.org/), or, we found that the example provided by [BigBlueButton](https://docs.bigbluebutton.org/2.2/setup-turn-server.html#generating-tls-certificates) covers the basics to configure Coturn with Let's Encrypt certs.
|
||||
> The Wiretrustee Management service can generate and maintain the certificates automatically, all you need to do is run the servicein a host with a public IP, configure a valid DNS record pointing to that IP and uncomment the 443 ports and command lines in the docker-compose.yml file.
|
||||
|
||||
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.
|
||||
|
||||
157
client/cmd/login.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
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"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
setupKey string
|
||||
|
||||
loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "login to the Wiretrustee Management Service (first run)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := internal.GetConfig(managementURL, configPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed getting config %s %v", configPath, err)
|
||||
//os.Exit(ExitSetupFailed)
|
||||
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())
|
||||
//os.Exit(ExitSetupFailed)
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
mgmTlsEnabled := false
|
||||
if config.ManagementURL.Scheme == "https" {
|
||||
mgmTlsEnabled = true
|
||||
}
|
||||
|
||||
log.Debugf("connecting to Management Service %s", config.ManagementURL.String())
|
||||
mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||
if err != nil {
|
||||
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
|
||||
//os.Exit(ExitSetupFailed)
|
||||
return err
|
||||
}
|
||||
log.Debugf("connected to anagement Service %s", config.ManagementURL.String())
|
||||
|
||||
serverKey, err := mgmClient.GetServerPublicKey()
|
||||
if err != nil {
|
||||
log.Errorf("failed while getting Management Service public key: %v", err)
|
||||
//os.Exit(ExitSetupFailed)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = loginPeer(*serverKey, mgmClient, setupKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed logging-in peer on Management Service : %v", err)
|
||||
//os.Exit(ExitSetupFailed)
|
||||
return err
|
||||
}
|
||||
|
||||
err = mgmClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Management Service client: %v", err)
|
||||
//os.Exit(ExitSetupFailed)
|
||||
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.Client, 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.Client, 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")
|
||||
loginResp, err := client.Register(serverPublicKey, validSetupKey.String())
|
||||
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()
|
||||
}
|
||||
|
||||
func init() {
|
||||
loginCmd.PersistentFlags().StringVar(&setupKey, "setup-key", "", "Setup key obtained from the Management Service Dashboard (used to register peer)")
|
||||
}
|
||||
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 WgIface %s got %s", iface.WgInterfaceDefault, actualConf.WgIface)
|
||||
}
|
||||
|
||||
if len(actualConf.PrivateKey) == 0 {
|
||||
t.Errorf("expected non empty Private key, got empty")
|
||||
}
|
||||
}
|
||||
72
client/cmd/root.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
// ExitSetupFailed defines exit code
|
||||
ExitSetupFailed = 1
|
||||
DefaultConfigPath = ""
|
||||
)
|
||||
|
||||
var (
|
||||
configPath string
|
||||
defaultConfigPath string
|
||||
logLevel string
|
||||
defaultLogFile string
|
||||
logFile string
|
||||
managementURL string
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "wiretrustee",
|
||||
Short: "",
|
||||
Long: "",
|
||||
}
|
||||
|
||||
// Execution control channel for stopCh signal
|
||||
stopCh chan int
|
||||
)
|
||||
|
||||
// Execute executes the root command.
|
||||
func Execute() error {
|
||||
return rootCmd.Execute()
|
||||
}
|
||||
func init() {
|
||||
|
||||
stopCh = make(chan int)
|
||||
|
||||
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.AddCommand(serviceCmd)
|
||||
rootCmd.AddCommand(upCmd)
|
||||
rootCmd.AddCommand(loginCmd)
|
||||
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)
|
||||
go func() {
|
||||
for range c {
|
||||
fmt.Println("\r- Ctrl+C pressed in Terminal")
|
||||
stopCh <- 0
|
||||
}
|
||||
}()
|
||||
}
|
||||
39
client/cmd/service.go
Normal file
@@ -0,0 +1,39 @@
|
||||
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",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
}
|
||||
117
client/cmd/service_controller.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/kardianos/service"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func (p *program) Start(s service.Service) error {
|
||||
// Start should not block. Do the actual work async.
|
||||
log.Info("starting service") //nolint
|
||||
go func() {
|
||||
err := upCmd.RunE(p.cmd, p.args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *program) Stop(s service.Service) error {
|
||||
stopCh <- 1
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
runCmd = &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "runs wiretrustee as service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
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",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
err = s.Start()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Printf("Wiretrustee service has been started")
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
stopCmd = &cobra.Command{
|
||||
Use: "stop",
|
||||
Short: "stops wiretrustee service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
err = s.Stop()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Printf("Wiretrustee service has been stopped")
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
restartCmd = &cobra.Command{
|
||||
Use: "restart",
|
||||
Short: "restarts wiretrustee service",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
err = s.Restart()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Printf("Wiretrustee service has been restarted")
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
}
|
||||
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",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
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 = s.Install()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Printf("Wiretrustee service has been installed")
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
uninstallCmd = &cobra.Command{
|
||||
Use: "uninstall",
|
||||
Short: "uninstalls wiretrustee service from system",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
s, err := newSVC(&program{}, newSVCConfig())
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = s.Uninstall()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
return
|
||||
}
|
||||
cmd.Printf("Wiretrustee has been uninstalled")
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
}
|
||||
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)
|
||||
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
|
||||
}
|
||||
169
client/cmd/up.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
||||
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
signal "github.com/wiretrustee/wiretrustee/signal/client"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var (
|
||||
upCmd = &cobra.Command{
|
||||
Use: "up",
|
||||
Short: "start wiretrustee",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed initializing log %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
engineConfig, err := createEngineConfig(myPrivateKey, config, loginResp.GetWiretrusteeConfig(), loginResp.GetPeerConfig())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// create start the Wiretrustee Engine that will connect to the Signal and Management streams and manage connections to remote peers.
|
||||
engine := internal.NewEngine(signalClient, mgmClient, engineConfig, cancel)
|
||||
err = engine.Start()
|
||||
if err != nil {
|
||||
log.Errorf("error while starting Wiretrustee Connection Engine: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
SetupCloseHandler()
|
||||
|
||||
select {
|
||||
case <-stopCh:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
log.Infof("receive signal to stop running")
|
||||
err = mgmClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Management Service client %v", err)
|
||||
return err
|
||||
}
|
||||
err = signalClient.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Signal Service client %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = engine.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("failed stopping engine %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
}
|
||||
|
||||
// createEngineConfig converts configuration received from Management Service to EngineConfig
|
||||
func createEngineConfig(key wgtypes.Key, config *internal.Config, wtConfig *mgmProto.WiretrusteeConfig, peerConfig *mgmProto.PeerConfig) (*internal.EngineConfig, error) {
|
||||
iFaceBlackList := make(map[string]struct{})
|
||||
for i := 0; i < len(config.IFaceBlackList); i += 2 {
|
||||
iFaceBlackList[config.IFaceBlackList[i]] = struct{}{}
|
||||
}
|
||||
|
||||
return &internal.EngineConfig{
|
||||
WgIface: config.WgIface,
|
||||
WgAddr: peerConfig.Address,
|
||||
IFaceBlackList: iFaceBlackList,
|
||||
WgPrivateKey: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// connectToSignal creates Signal Service client and established a connection
|
||||
func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, ourPrivateKey wgtypes.Key) (*signal.Client, 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.Client, *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.Infof("peer logged in to Management Service %s", managementAddr)
|
||||
|
||||
return client, loginResp, nil
|
||||
}
|
||||
110
client/cmd/up_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
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_ShouldFail_On_NoConfig(t *testing.T) {
|
||||
|
||||
tempDir := t.TempDir()
|
||||
confPath := tempDir + "/config.json"
|
||||
mgmtURL := fmt.Sprintf("http://%s", mgmAddr)
|
||||
rootCmd.SetArgs([]string{
|
||||
"up",
|
||||
"--config",
|
||||
confPath,
|
||||
"--management-url",
|
||||
mgmtURL,
|
||||
})
|
||||
err := rootCmd.Execute()
|
||||
if err == nil || !errors.Is(err, os.ErrNotExist) {
|
||||
t.Errorf("expecting login command to fail on absence of config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUp(t *testing.T) {
|
||||
|
||||
defer iface.Close()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
confPath := tempDir + "/config.json"
|
||||
mgmtURL, err := url.Parse("http://" + mgmAddr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rootCmd.SetArgs([]string{
|
||||
"login",
|
||||
"--config",
|
||||
confPath,
|
||||
"--setup-key",
|
||||
"A2C8E62B-38F5-4553-B31E-DD66C696CEBB",
|
||||
"--management-url",
|
||||
mgmtURL.String(),
|
||||
})
|
||||
err = rootCmd.Execute()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rootCmd.SetArgs([]string{
|
||||
"up",
|
||||
"--config",
|
||||
confPath,
|
||||
})
|
||||
go func() {
|
||||
err = rootCmd.Execute()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error while running up command, got %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
exists := false
|
||||
for start := time.Now(); time.Since(start) < 15*time.Second; {
|
||||
e, err := iface.Exists(iface.WgInterfaceDefault)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if *e {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if !exists {
|
||||
t.Errorf("expected wireguard interface %s to be created", iface.WgInterfaceDefault)
|
||||
}
|
||||
}
|
||||
118
client/installer.nsis
Normal file
@@ -0,0 +1,118 @@
|
||||
!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 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
|
||||
@@ -1,4 +1,4 @@
|
||||
package connection
|
||||
package internal
|
||||
|
||||
import "sync"
|
||||
|
||||
113
client/internal/config.go
Normal file
@@ -0,0 +1,113 @@
|
||||
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
|
||||
ManagementURL *url.URL
|
||||
WgIface string
|
||||
IFaceBlackList []string
|
||||
}
|
||||
|
||||
//createNewConfig creates a new config generating a new Wireguard key and saving to file
|
||||
func createNewConfig(managementURL string, configPath string) (*Config, error) {
|
||||
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
|
||||
}
|
||||
|
||||
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) (*Config, error) {
|
||||
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
log.Infof("generating new config %s", configPath)
|
||||
return createNewConfig(managementURL, configPath)
|
||||
} else {
|
||||
return ReadConfig(managementURL, configPath)
|
||||
}
|
||||
}
|
||||
|
||||
// generateKey generates a new Wireguard private key
|
||||
func generateKey() string {
|
||||
key, err := wgtypes.GenerateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return key.String()
|
||||
}
|
||||
@@ -1,24 +1,56 @@
|
||||
package connection
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/pion/ice/v2"
|
||||
ice "github.com/pion/ice/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultWgKeepAlive default Wireguard keep alive constant
|
||||
DefaultWgKeepAlive = 20 * time.Second
|
||||
privateIPBlocks []*net.IPNet
|
||||
)
|
||||
|
||||
type Status string
|
||||
|
||||
const (
|
||||
StatusConnected Status = "Connected"
|
||||
StatusConnecting Status = "Connecting"
|
||||
StatusDisconnected Status = "Disconnected"
|
||||
)
|
||||
|
||||
func init() {
|
||||
for _, cidr := range []string{
|
||||
"127.0.0.0/8", // IPv4 loopback
|
||||
"10.0.0.0/8", // RFC1918
|
||||
"172.16.0.0/12", // RFC1918
|
||||
"192.168.0.0/16", // RFC1918
|
||||
"169.254.0.0/16", // RFC3927 link-local
|
||||
"::1/128", // IPv6 loopback
|
||||
"fe80::/10", // IPv6 link-local
|
||||
"fc00::/7", // IPv6 unique local addr
|
||||
} {
|
||||
_, block, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("parse error on %q: %v", cidr, err))
|
||||
}
|
||||
privateIPBlocks = append(privateIPBlocks, block)
|
||||
}
|
||||
}
|
||||
|
||||
// ConnConfig Connection configuration struct
|
||||
type ConnConfig struct {
|
||||
// Local Wireguard listening address e.g. 127.0.0.1:51820
|
||||
WgListenAddr string
|
||||
// A Local Wireguard Peer IP address in CIDR notation e.g. 10.30.30.1/24
|
||||
WgPeerIp string
|
||||
WgPeerIP string
|
||||
// Local Wireguard Interface name (e.g. wg0)
|
||||
WgIface string
|
||||
// Wireguard allowed IPs (e.g. 10.30.30.2/32)
|
||||
@@ -29,13 +61,17 @@ type ConnConfig struct {
|
||||
RemoteWgKey wgtypes.Key
|
||||
|
||||
StunTurnURLS []*ice.URL
|
||||
|
||||
iFaceBlackList map[string]struct{}
|
||||
}
|
||||
|
||||
// IceCredentials ICE protocol credentials struct
|
||||
type IceCredentials struct {
|
||||
uFrag string
|
||||
pwd string
|
||||
}
|
||||
|
||||
// Connection Holds information about a connection and handles signal protocol
|
||||
type Connection struct {
|
||||
Config ConnConfig
|
||||
// signalCandidate is a handler function to signal remote peer about local connection candidate
|
||||
@@ -59,8 +95,11 @@ type Connection struct {
|
||||
closeCond *Cond
|
||||
|
||||
remoteAuthCond sync.Once
|
||||
|
||||
Status Status
|
||||
}
|
||||
|
||||
// NewConnection Creates a new connection and sets handling functions for signal protocol
|
||||
func NewConnection(config ConnConfig,
|
||||
signalCandidate func(candidate ice.Candidate) error,
|
||||
signalOffer func(uFrag string, pwd string) error,
|
||||
@@ -77,6 +116,7 @@ func NewConnection(config ConnConfig,
|
||||
connected: NewCond(),
|
||||
agent: nil,
|
||||
wgProxy: NewWgProxy(config.WgIface, config.RemoteWgKey.String(), config.WgAllowedIPs, config.WgListenAddr),
|
||||
Status: StatusDisconnected,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +126,17 @@ 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,
|
||||
// MulticastDNSMode: ice.MulticastDNSModeQueryAndGather,
|
||||
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4},
|
||||
Urls: conn.Config.StunTurnURLS,
|
||||
CandidateTypes: []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay},
|
||||
InterfaceFilter: func(s string) bool {
|
||||
if conn.Config.iFaceBlackList == nil {
|
||||
return true
|
||||
}
|
||||
_, ok := conn.Config.iFaceBlackList[s]
|
||||
return !ok
|
||||
},
|
||||
})
|
||||
conn.agent = a
|
||||
|
||||
@@ -110,6 +159,7 @@ func (conn *Connection) Open(timeout time.Duration) error {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.Status = StatusConnecting
|
||||
log.Infof("trying to connect to peer %s", conn.Config.RemoteWgKey.String())
|
||||
|
||||
// wait until credentials have been sent from the remote peer (will arrive via a signal server)
|
||||
@@ -124,33 +174,99 @@ func (conn *Connection) Open(timeout time.Duration) error {
|
||||
}
|
||||
|
||||
isControlling := conn.Config.WgKey.PublicKey().String() > conn.Config.RemoteWgKey.String()
|
||||
remoteConn, err := conn.openConnectionToRemote(isControlling, remoteAuth)
|
||||
var remoteConn *ice.Conn
|
||||
remoteConn, err = conn.openConnectionToRemote(isControlling, remoteAuth)
|
||||
if err != nil {
|
||||
log.Errorf("failed establishing connection with the remote peer %s %s", conn.Config.RemoteWgKey.String(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.wgProxy.Start(remoteConn)
|
||||
var pair *ice.CandidatePair
|
||||
pair, err = conn.agent.GetSelectedCandidatePair()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// in case the remote peer is in the local network or one of the peers has public static IP -> no need for a Wireguard proxy, direct communication is possible.
|
||||
if !useProxy(pair) {
|
||||
log.Debugf("it is possible to establish a direct connection (without proxy) to peer %s - my addr: %s, remote addr: %s", conn.Config.RemoteWgKey.String(), pair.Local, pair.Remote)
|
||||
err = conn.wgProxy.StartLocal(fmt.Sprintf("%s:%d", pair.Remote.Address(), iface.WgPort))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Infof("establishing secure tunnel to peer %s via selected candidate pair %s", conn.Config.RemoteWgKey.String(), pair)
|
||||
err = conn.wgProxy.Start(remoteConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pair.Remote.Type() == ice.CandidateTypeRelay || pair.Local.Type() == ice.CandidateTypeRelay {
|
||||
log.Infof("using relay with peer %s", conn.Config.RemoteWgKey)
|
||||
}
|
||||
|
||||
conn.Status = StatusConnected
|
||||
log.Infof("opened connection to peer %s", conn.Config.RemoteWgKey.String())
|
||||
case <-conn.closeCond.C:
|
||||
conn.Status = StatusDisconnected
|
||||
return fmt.Errorf("connection to peer %s has been closed", conn.Config.RemoteWgKey.String())
|
||||
case <-time.After(timeout):
|
||||
err := conn.Close()
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
log.Warnf("error while closing connection to peer %s -> %s", conn.Config.RemoteWgKey.String(), err.Error())
|
||||
}
|
||||
conn.Status = StatusDisconnected
|
||||
return fmt.Errorf("timeout of %vs exceeded while waiting for the remote peer %s", timeout.Seconds(), conn.Config.RemoteWgKey.String())
|
||||
}
|
||||
|
||||
// wait until connection has been closed
|
||||
select {
|
||||
case <-conn.closeCond.C:
|
||||
return fmt.Errorf("connection to peer %s has been closed", conn.Config.RemoteWgKey.String())
|
||||
}
|
||||
<-conn.closeCond.C
|
||||
conn.Status = StatusDisconnected
|
||||
return fmt.Errorf("connection to peer %s has been closed", conn.Config.RemoteWgKey.String())
|
||||
}
|
||||
|
||||
func isPublicIP(ip net.IP) bool {
|
||||
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, block := range privateIPBlocks {
|
||||
if block.Contains(ip) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//useProxy determines whether a direct connection (without a go proxy) is possible
|
||||
//There are 3 cases: one of the peers has a public IP or both peers are in the same private network
|
||||
//Please note, that this check happens when peers were already able to ping each other with ICE layer.
|
||||
func useProxy(pair *ice.CandidatePair) bool {
|
||||
remoteIP := net.ParseIP(pair.Remote.Address())
|
||||
myIp := net.ParseIP(pair.Local.Address())
|
||||
remoteIsPublic := isPublicIP(remoteIP)
|
||||
myIsPublic := isPublicIP(myIp)
|
||||
|
||||
//one of the hosts has a public IP
|
||||
if remoteIsPublic && pair.Remote.Type() == ice.CandidateTypeHost {
|
||||
return false
|
||||
}
|
||||
if myIsPublic && pair.Local.Type() == ice.CandidateTypeHost {
|
||||
return false
|
||||
}
|
||||
|
||||
if pair.Local.Type() == ice.CandidateTypeHost && pair.Remote.Type() == ice.CandidateTypeHost {
|
||||
if !remoteIsPublic && !myIsPublic {
|
||||
//both hosts are in the same private network
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Close Closes a peer connection
|
||||
func (conn *Connection) Close() error {
|
||||
var err error
|
||||
conn.closeCond.Do(func() {
|
||||
@@ -176,6 +292,7 @@ func (conn *Connection) Close() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// OnAnswer Handles the answer from the other peer
|
||||
func (conn *Connection) OnAnswer(remoteAuth IceCredentials) error {
|
||||
|
||||
conn.remoteAuthCond.Do(func() {
|
||||
@@ -185,23 +302,25 @@ func (conn *Connection) OnAnswer(remoteAuth IceCredentials) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnOffer Handles the offer from the other peer
|
||||
func (conn *Connection) OnOffer(remoteAuth IceCredentials) error {
|
||||
|
||||
conn.remoteAuthCond.Do(func() {
|
||||
log.Debugf("OnOffer from peer %s", conn.Config.RemoteWgKey.String())
|
||||
conn.remoteAuthChannel <- remoteAuth
|
||||
uFrag, pwd, err := conn.agent.GetLocalUserCredentials()
|
||||
if err != nil {
|
||||
if err != nil { //nolint
|
||||
}
|
||||
|
||||
err = conn.signalAnswer(uFrag, pwd)
|
||||
if err != nil {
|
||||
if err != nil { //nolint
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnRemoteCandidate Handles remote candidate provided by the peer.
|
||||
func (conn *Connection) OnRemoteCandidate(candidate ice.Candidate) error {
|
||||
|
||||
log.Debugf("onRemoteCandidate from peer %s -> %s", conn.Config.RemoteWgKey.String(), candidate.String())
|
||||
@@ -280,9 +399,8 @@ func (conn *Connection) listenOnConnectionStateChanges() error {
|
||||
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)
|
||||
log.Debugf("ICE connected to peer %s via a selected connnection candidate pair %s", conn.Config.RemoteWgKey.String(), pair)
|
||||
} else if state == ice.ConnectionStateDisconnected || state == ice.ConnectionStateFailed {
|
||||
// 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())
|
||||
456
client/internal/engine.go
Normal file
@@ -0,0 +1,456 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
ice "github.com/pion/ice/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
||||
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
signal "github.com/wiretrustee/wiretrustee/signal/client"
|
||||
sProto "github.com/wiretrustee/wiretrustee/signal/proto"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PeerConnectionTimeout is a timeout of an initial connection attempt to a remote peer.
|
||||
// E.g. this peer will wait PeerConnectionTimeout for the remote peer to respond, if not successful then it will retry the connection attempt.
|
||||
const PeerConnectionTimeout = 40 * time.Second
|
||||
|
||||
// EngineConfig is a config for the Engine
|
||||
type EngineConfig struct {
|
||||
WgIface 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{}
|
||||
}
|
||||
|
||||
// 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
|
||||
// conns is a collection of remote peer connections indexed by local public key of the remote peers
|
||||
conns map[string]*Connection
|
||||
|
||||
// peerMux is used to sync peer operations (e.g. open connection, peer removal)
|
||||
peerMux *sync.Mutex
|
||||
// syncMsgMux is used to guarantee sequential Management Service message processing
|
||||
syncMsgMux *sync.Mutex
|
||||
|
||||
config *EngineConfig
|
||||
|
||||
// wgPort is a Wireguard local listen port
|
||||
wgPort int
|
||||
|
||||
// STUNs is a list of STUN servers used by ICE
|
||||
STUNs []*ice.URL
|
||||
// TURNs is a list of STUN servers used by ICE
|
||||
TURNs []*ice.URL
|
||||
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// 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) *Engine {
|
||||
return &Engine{
|
||||
signal: signalClient,
|
||||
mgmClient: mgmClient,
|
||||
conns: map[string]*Connection{},
|
||||
peerMux: &sync.Mutex{},
|
||||
syncMsgMux: &sync.Mutex{},
|
||||
config: config,
|
||||
STUNs: []*ice.URL{},
|
||||
TURNs: []*ice.URL{},
|
||||
cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Engine) Stop() error {
|
||||
log.Debugf("removing Wiretrustee interface %s", e.config.WgIface)
|
||||
err := iface.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed closing Wiretrustee interface %s %v", e.config.WgIface, err)
|
||||
return err
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
wgIface := e.config.WgIface
|
||||
wgAddr := e.config.WgAddr
|
||||
myPrivateKey := e.config.WgPrivateKey
|
||||
|
||||
err := iface.Create(wgIface, wgAddr)
|
||||
if err != nil {
|
||||
log.Errorf("failed creating interface %s: [%s]", wgIface, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = iface.Configure(wgIface, myPrivateKey.String())
|
||||
if err != nil {
|
||||
log.Errorf("failed configuring Wireguard interface [%s]: %s", wgIface, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
port, err := iface.GetListenPort(wgIface)
|
||||
if err != nil {
|
||||
log.Errorf("failed getting Wireguard listen port [%s]: %s", wgIface, err.Error())
|
||||
return err
|
||||
}
|
||||
e.wgPort = *port
|
||||
|
||||
e.receiveSignalEvents()
|
||||
e.receiveManagementEvents()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initializePeer peer agent attempt to open connection
|
||||
func (e *Engine) initializePeer(peer Peer) {
|
||||
var backOff = &backoff.ExponentialBackOff{
|
||||
InitialInterval: backoff.DefaultInitialInterval,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 5 * time.Second,
|
||||
MaxElapsedTime: time.Duration(0), //never stop
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
operation := func() error {
|
||||
_, err := e.openPeerConnection(e.wgPort, e.config.WgPrivateKey, peer)
|
||||
e.peerMux.Lock()
|
||||
defer e.peerMux.Unlock()
|
||||
if _, ok := e.conns[peer.WgPubKey]; !ok {
|
||||
log.Infof("removing connection attempt with Peer: %v, not retrying", peer.WgPubKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Warnln(err)
|
||||
log.Warnln("retrying connection because of error: ", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, backOff)
|
||||
if err != nil {
|
||||
// should actually never happen
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Engine) removePeerConnections(peers []string) error {
|
||||
e.peerMux.Lock()
|
||||
defer e.peerMux.Unlock()
|
||||
for _, peer := range peers {
|
||||
err := e.removePeerConnection(peer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// removePeerConnection closes existing peer connection and removes peer
|
||||
func (e *Engine) removePeerConnection(peerKey string) error {
|
||||
conn, exists := e.conns[peerKey]
|
||||
if exists && conn != nil {
|
||||
delete(e.conns, peerKey)
|
||||
return conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPeerConnectionStatus returns a connection Status or nil if peer connection wasn't found
|
||||
func (e *Engine) GetPeerConnectionStatus(peerKey string) *Status {
|
||||
e.peerMux.Lock()
|
||||
defer e.peerMux.Unlock()
|
||||
|
||||
conn, exists := e.conns[peerKey]
|
||||
if exists && conn != nil {
|
||||
return &conn.Status
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// openPeerConnection opens a new remote peer connection
|
||||
func (e *Engine) openPeerConnection(wgPort int, myKey wgtypes.Key, peer Peer) (*Connection, error) {
|
||||
e.peerMux.Lock()
|
||||
|
||||
remoteKey, _ := wgtypes.ParseKey(peer.WgPubKey)
|
||||
connConfig := &ConnConfig{
|
||||
WgListenAddr: fmt.Sprintf("127.0.0.1:%d", wgPort),
|
||||
WgPeerIP: e.config.WgAddr,
|
||||
WgIface: e.config.WgIface,
|
||||
WgAllowedIPs: peer.WgAllowedIps,
|
||||
WgKey: myKey,
|
||||
RemoteWgKey: remoteKey,
|
||||
StunTurnURLS: append(e.STUNs, e.TURNs...),
|
||||
iFaceBlackList: e.config.IFaceBlackList,
|
||||
}
|
||||
|
||||
signalOffer := func(uFrag string, pwd string) error {
|
||||
return signalAuth(uFrag, pwd, myKey, remoteKey, e.signal, false)
|
||||
}
|
||||
|
||||
signalAnswer := func(uFrag string, pwd string) error {
|
||||
return signalAuth(uFrag, pwd, myKey, remoteKey, e.signal, true)
|
||||
}
|
||||
signalCandidate := func(candidate ice.Candidate) error {
|
||||
return signalCandidate(candidate, myKey, remoteKey, e.signal)
|
||||
}
|
||||
conn := NewConnection(*connConfig, signalCandidate, signalOffer, signalAnswer)
|
||||
e.conns[remoteKey.String()] = conn
|
||||
e.peerMux.Unlock()
|
||||
|
||||
// blocks until the connection is open (or timeout)
|
||||
err := conn.Open(PeerConnectionTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// receiveManagementEvents connects to the Management Service event stream to receive updates from the management service
|
||||
// E.g. when a new peer has been registered and we are allowed to connect to it.
|
||||
func (e *Engine) receiveManagementEvents() {
|
||||
go func() {
|
||||
err := e.mgmClient.Sync(func(update *mgmProto.SyncResponse) error {
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
if update.GetWiretrusteeConfig() != nil {
|
||||
err := e.updateTURNs(update.GetWiretrusteeConfig().GetTurns())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.updateSTUNs(update.GetWiretrusteeConfig().GetStuns())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//todo update signal
|
||||
}
|
||||
|
||||
if update.GetRemotePeers() != nil || update.GetRemotePeersIsEmpty() {
|
||||
// empty arrays are serialized by protobuf to null, but for our case empty array is a valid state.
|
||||
err := e.updatePeers(update.GetRemotePeers())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
e.cancel()
|
||||
return
|
||||
}
|
||||
log.Infof("connected to Management Service updates stream")
|
||||
}()
|
||||
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) updatePeers(remotePeers []*mgmProto.RemotePeerConfig) error {
|
||||
log.Debugf("got peers update from Management Service, updating")
|
||||
remotePeerMap := make(map[string]struct{})
|
||||
for _, peer := range remotePeers {
|
||||
remotePeerMap[peer.GetWgPubKey()] = struct{}{}
|
||||
}
|
||||
|
||||
//remove peers that are no longer available for us
|
||||
toRemove := []string{}
|
||||
for p := range e.conns {
|
||||
if _, ok := remotePeerMap[p]; !ok {
|
||||
toRemove = append(toRemove, p)
|
||||
}
|
||||
}
|
||||
err := e.removePeerConnections(toRemove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add new peers
|
||||
for _, peer := range remotePeers {
|
||||
peerKey := peer.GetWgPubKey()
|
||||
peerIPs := peer.GetAllowedIps()
|
||||
if _, ok := e.conns[peerKey]; !ok {
|
||||
go e.initializePeer(Peer{
|
||||
WgPubKey: peerKey,
|
||||
WgAllowedIps: strings.Join(peerIPs, ","),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// receiveSignalEvents connects to the Signal Service event stream to negotiate connection with remote peers
|
||||
func (e *Engine) receiveSignalEvents() {
|
||||
// connect to a stream of messages coming from the signal server
|
||||
e.signal.Receive(func(msg *sProto.Message) error {
|
||||
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
conn := e.conns[msg.Key]
|
||||
if conn == nil {
|
||||
return fmt.Errorf("wrongly addressed message %s", msg.Key)
|
||||
}
|
||||
|
||||
if conn.Config.RemoteWgKey.String() != msg.Key {
|
||||
return fmt.Errorf("unknown peer %s", msg.Key)
|
||||
}
|
||||
|
||||
switch msg.GetBody().Type {
|
||||
case sProto.Body_OFFER:
|
||||
remoteCred, err := signal.UnMarshalCredential(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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,12 +1,13 @@
|
||||
package connection
|
||||
package internal
|
||||
|
||||
import (
|
||||
"github.com/pion/ice/v2"
|
||||
ice "github.com/pion/ice/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/iface"
|
||||
"net"
|
||||
)
|
||||
|
||||
// WgProxy an instance of an instance of the Connection Wireguard Proxy
|
||||
type WgProxy struct {
|
||||
iface string
|
||||
remoteKey string
|
||||
@@ -16,6 +17,7 @@ type WgProxy struct {
|
||||
wgConn net.Conn
|
||||
}
|
||||
|
||||
// NewWgProxy creates a new Connection Wireguard Proxy
|
||||
func NewWgProxy(iface string, remoteKey string, allowedIps string, wgAddr string) *WgProxy {
|
||||
return &WgProxy{
|
||||
iface: iface,
|
||||
@@ -26,6 +28,7 @@ func NewWgProxy(iface string, remoteKey string, allowedIps string, wgAddr string
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the proxy
|
||||
func (p *WgProxy) Close() error {
|
||||
|
||||
close(p.close)
|
||||
@@ -35,10 +38,25 @@ func (p *WgProxy) Close() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := iface.RemovePeer(p.iface, p.remoteKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartLocal configure the interface with a peer using a direct IP:Port endpoint to the remote host
|
||||
func (p *WgProxy) StartLocal(host string) error {
|
||||
err := iface.UpdatePeer(p.iface, p.remoteKey, p.allowedIps, DefaultWgKeepAlive, host)
|
||||
if err != nil {
|
||||
log.Errorf("error while configuring Wireguard peer [%s] %s", p.remoteKey, err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts a new proxy using the ICE connection
|
||||
func (p *WgProxy) Start(remoteConn *ice.Conn) error {
|
||||
|
||||
wgConn, err := net.Dial("udp", p.wgAddr)
|
||||
@@ -78,9 +96,10 @@ func (p *WgProxy) proxyToRemotePeer(remoteConn *ice.Conn) {
|
||||
continue
|
||||
}
|
||||
|
||||
n, err = remoteConn.Write(buf[:n])
|
||||
_, err = remoteConn.Write(buf[:n])
|
||||
if err != nil {
|
||||
//log.Warnln("failed writing to remote peer: ", err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,11 +119,13 @@ func (p *WgProxy) proxyToLocalWireguard(remoteConn *ice.Conn) {
|
||||
n, err := remoteConn.Read(buf)
|
||||
if err != nil {
|
||||
//log.Errorf("failed reading from remote connection %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
n, err = p.wgConn.Write(buf[:n])
|
||||
_, err = p.wgConn.Write(buf[:n])
|
||||
if err != nil {
|
||||
//log.Errorf("failed writing to local Wireguard instance %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wiretrustee/wiretrustee/cmd"
|
||||
"github.com/wiretrustee/wiretrustee/client/cmd"
|
||||
"os"
|
||||
)
|
||||
|
||||
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
|
||||
wintun.dll RCDATA wintun.dll
|
||||
|
||||
BIN
client/resources_windows_amd64.syso
Normal file
14
client/system/info.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package system
|
||||
|
||||
//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
|
||||
}
|
||||
39
client/system/info_darwin.go
Normal file
@@ -0,0 +1,39 @@
|
||||
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()
|
||||
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()
|
||||
}
|
||||
39
client/system/info_freebsd.go
Normal file
@@ -0,0 +1,39 @@
|
||||
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()
|
||||
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()
|
||||
}
|
||||
76
client/system/info_linux.go
Normal file
@@ -0,0 +1,76 @@
|
||||
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()
|
||||
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()
|
||||
}
|
||||
35
client/system/info_windows.go
Normal file
@@ -0,0 +1,35 @@
|
||||
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()
|
||||
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>"
|
||||
}
|
||||
}
|
||||
27
client/testdata/store.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 |
@@ -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,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()
|
||||
}
|
||||
31
docs/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
## Introduction
|
||||
|
||||
Wiretrustee is a WireGuard®-based platform that connects your devices securely into a peer-to-peer private network.
|
||||
|
||||
It simplifies VPN creation and management for your organization without the hassle of opening ports, complex firewall rules, and so forth.
|
||||
|
||||
It literally takes less than 5 minutes to provision a secure peer-to-peer VPN with Wiretrustee.
|
||||
|
||||
### High-level 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 [beta.wiretrustee.com](https://beta.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: 39 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://beta.wiretrustee.com/](https://beta.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.
|
||||
92
docs/self-hosting.md
Normal file
@@ -0,0 +1,92 @@
|
||||
### 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).
|
||||
|
||||
### 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, 3478``` (Dashboard, Management HTTP API, Management gRpc API, 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 creating a new domain.
|
||||
|
||||
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;
|
||||
}
|
||||
21
go.mod
@@ -4,15 +4,24 @@ go 1.16
|
||||
|
||||
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/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/kardianos/service v1.2.0
|
||||
github.com/onsi/ginkgo v1.16.4
|
||||
github.com/onsi/gomega v1.13.0
|
||||
github.com/pion/ice/v2 v2.1.7
|
||||
github.com/rs/cors v1.8.0
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/spf13/cobra v1.1.3
|
||||
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
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
|
||||
golang.zx2c4.com/wireguard/windows v0.4.5
|
||||
google.golang.org/grpc v1.32.0
|
||||
google.golang.org/protobuf v1.26.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
)
|
||||
|
||||
164
go.sum
@@ -11,6 +11,7 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
@@ -45,14 +46,23 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@@ -67,18 +77,22 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/nftables v0.0.0-20201230142148-715e31cb3c31 h1:kyEB9geFhgDyawmvavtNu9iGW9ri/iq54XTSNIEeHxI=
|
||||
github.com/google/nftables v0.0.0-20201230142148-715e31cb3c31/go.mod h1:cfspEyr/Ap+JDIITA+N9a0ernqG0qZ4W1aqMRgDZa1g=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
@@ -87,6 +101,8 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
@@ -111,36 +127,58 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4 h1:nwOc1YaOrYJ37sEBrtWZrdqzK22hiJs3GpDmP3sR2Yw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g=
|
||||
github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d h1:MFX8DxRnKMY/2M3H61iSsVbo/n3h0MWGmWNN1UViOU0=
|
||||
github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d/go.mod h1:QHb4k4cr1fQikUahfcRVPcEXiUgFsdIstGqlurL0XL4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v0.0.0-20191009155606-de872b0d824b/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkfg=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
|
||||
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
|
||||
github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0=
|
||||
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||
@@ -152,10 +190,24 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pion/dtls/v2 v2.0.9 h1:7Ow+V++YSZQMYzggI0P9vLJz/hUFcffsfGMfT/Qy+u8=
|
||||
@@ -195,6 +247,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so=
|
||||
github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
@@ -219,17 +273,21 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
@@ -241,12 +299,13 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -265,8 +324,10 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -280,17 +341,23 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c h1:KHUzaHIpjWVlVVNh65G3hhuj3KB1HnjY6Cq5cTvRQT8=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -299,9 +366,11 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -314,25 +383,44 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
|
||||
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7-0.20210524175448-3115f89c4b99 h1:ZEXtoJu1S0ie/EmdYnjY3CqaCCZxnldL+K1ftMITD2Q=
|
||||
golang.org/x/text v0.3.7-0.20210524175448-3115f89c4b99/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -353,15 +441,20 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wireguard v0.0.20200121/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4=
|
||||
golang.zx2c4.com/wireguard v0.0.20201118 h1:QL8y2C7uO8T6z1GY+UX/hSeWiYEBurQkXjOTRFtCvXU=
|
||||
golang.zx2c4.com/wireguard v0.0.20201118/go.mod h1:Dz+cq5bnrai9EpgYj4GDof/+qaGzbRWbeaAOs1bUYa0=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b h1:l4mBVCYinjzZuR5DtxHuBD6wyd4348TGiavJ5vLrhEc=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b/go.mod h1:UdS9frhv65KTfwxME1xE8+rHYoFpbm36gOud1GhBe9c=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 h1:ab2jcw2W91Rz07eHAb8Lic7sFQKO0NhBftjv6m/gL/0=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
|
||||
golang.zx2c4.com/wireguard/windows v0.4.5 h1:btpw+5IM8UrSl5SILCODt1bXTM2qYpcaYArM6wDlwHA=
|
||||
golang.zx2c4.com/wireguard/windows v0.4.5/go.mod h1:LdS2bRTWu//RpztraGz5ZkPZul60cCbmgtLTPSKrS50=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
@@ -393,18 +486,31 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
223
iface/iface.go
@@ -2,9 +2,8 @@ package iface
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"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"
|
||||
@@ -16,90 +15,116 @@ const (
|
||||
defaultMTU = 1280
|
||||
)
|
||||
|
||||
// Saves tun device object - is it required?
|
||||
var tunIface tun.Device
|
||||
var (
|
||||
tunIface tun.Device
|
||||
// todo check after move the WgPort constant to the client
|
||||
WgPort = 51820
|
||||
)
|
||||
|
||||
// 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 {
|
||||
// CreateWithUserspace Creates a new Wireguard interface, using wireguard-go userspace implementation
|
||||
func CreateWithUserspace(iface string, address string) error {
|
||||
var err error
|
||||
|
||||
tunIface, err = tun.CreateTUN(iface, defaultMTU)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We need to create a wireguard-go device and listen to configuration requests
|
||||
tunDevice := device.NewDevice(tunIface, device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
|
||||
tunDevice.Up()
|
||||
tunSock, err := ipc.UAPIOpen(iface)
|
||||
tunDevice := device.NewDevice(tunIface, conn.NewDefaultBind(), device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
|
||||
err = tunDevice.Up()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uapi, err := ipc.UAPIListen(iface, tunSock)
|
||||
uapi, err := getUAPI(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := uapi.Accept()
|
||||
uapiConn, err := uapi.Accept()
|
||||
if err != nil {
|
||||
log.Debugln(err)
|
||||
return
|
||||
log.Debugln("uapi Accept failed with error: ", err)
|
||||
continue
|
||||
}
|
||||
go tunDevice.IpcHandle(conn)
|
||||
go tunDevice.IpcHandle(uapiConn)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debugln("UAPI listener started")
|
||||
|
||||
err = assignAddr(iface, address)
|
||||
err = assignAddr(address, iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return 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()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &key, Configure(iface, key.String())
|
||||
}
|
||||
|
||||
// Configures a Wireguard interface
|
||||
// The interface must exist before calling this method (e.g. call interface.Create() before)
|
||||
func Configure(iface string, privateKey string) error {
|
||||
|
||||
log.Debugf("configuring Wireguard interface %s", iface)
|
||||
// configure peer for the wireguard device
|
||||
func configureDevice(iface string, config wgtypes.Config) error {
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wg.Close()
|
||||
|
||||
_, err = wg.Device(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("got Wireguard device %s", iface)
|
||||
|
||||
return wg.ConfigureDevice(iface, config)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
exists = false
|
||||
return &exists, nil
|
||||
}
|
||||
|
||||
// Configure configures a Wireguard interface
|
||||
// The interface must exist before calling this method (e.g. call interface.Create() before)
|
||||
func Configure(iface string, privateKey string) error {
|
||||
|
||||
log.Debugf("configuring Wireguard interface %s", iface)
|
||||
|
||||
log.Debugf("adding Wireguard private key")
|
||||
key, err := wgtypes.ParseKey(privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fwmark := 0
|
||||
cfg := wgtypes.Config{
|
||||
p := WgPort
|
||||
config := wgtypes.Config{
|
||||
PrivateKey: &key,
|
||||
ReplacePeers: false,
|
||||
FirewallMark: &fwmark,
|
||||
}
|
||||
err = wg.ConfigureDevice(iface, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
ListenPort: &p,
|
||||
}
|
||||
|
||||
return nil
|
||||
return configureDevice(iface, config)
|
||||
}
|
||||
|
||||
// GetListenPort returns the listening port of the Wireguard endpoint
|
||||
func GetListenPort(iface string) (*int, error) {
|
||||
log.Debugf("getting Wireguard listen port of interface %s", iface)
|
||||
|
||||
@@ -114,88 +139,39 @@ func GetListenPort(iface string) (*int, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("got Wireguard device listen port %s, %d", iface, &d.ListenPort)
|
||||
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
|
||||
// UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist
|
||||
// Endpoint is optional
|
||||
func UpdatePeer(iface string, peerKey string, allowedIps string, keepAlive time.Duration, endpoint string) error {
|
||||
|
||||
log.Debugf("updating interface %s peer %s: endpoint %s ", iface, peerKey, endpoint)
|
||||
|
||||
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)
|
||||
_, ipNet, err := net.ParseCIDR(allowedIps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
|
||||
peers := make([]wgtypes.PeerConfig, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
peer := wgtypes.PeerConfig{
|
||||
PublicKey: peerKeyParsed,
|
||||
ReplaceAllowedIPs: true,
|
||||
AllowedIPs: []net.IPNet{*ipNet},
|
||||
PersistentKeepaliveInterval: &keepAlive,
|
||||
}
|
||||
peers = append(peers, peer)
|
||||
|
||||
config := wgtypes.Config{
|
||||
ReplacePeers: false,
|
||||
Peers: peers,
|
||||
Peers: []wgtypes.PeerConfig{peer},
|
||||
}
|
||||
err = wg.ConfigureDevice(iface, config)
|
||||
|
||||
err = configureDevice(iface, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -207,24 +183,12 @@ func UpdatePeer(iface string, peerKey string, allowedIps string, keepAlive time.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Updates a Wireguard interface Peer with the new endpoint
|
||||
// UpdatePeerEndpoint updates a Wireguard interface Peer with the new endpoint
|
||||
// Used when NAT hole punching was successful and an update of the remote peer endpoint is required
|
||||
func UpdatePeerEndpoint(iface string, peerKey string, newEndpoint string) error {
|
||||
|
||||
log.Debugf("updating peer %s endpoint %s ", peerKey, newEndpoint)
|
||||
|
||||
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
|
||||
@@ -233,35 +197,44 @@ func UpdatePeerEndpoint(iface string, peerKey string, newEndpoint string) error
|
||||
log.Debugf("parsed peer endpoint [%s]", peerAddr.String())
|
||||
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
peers := make([]wgtypes.PeerConfig, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peer := wgtypes.PeerConfig{
|
||||
PublicKey: peerKeyParsed,
|
||||
ReplaceAllowedIPs: false,
|
||||
UpdateOnly: true,
|
||||
Endpoint: peerAddr,
|
||||
}
|
||||
peers = append(peers, peer)
|
||||
|
||||
config := wgtypes.Config{
|
||||
ReplacePeers: false,
|
||||
Peers: peers,
|
||||
Peers: []wgtypes.PeerConfig{peer},
|
||||
}
|
||||
err = wg.ConfigureDevice(iface, config)
|
||||
return configureDevice(iface, config)
|
||||
}
|
||||
|
||||
// RemovePeer removes a Wireguard Peer from the interface iface
|
||||
func RemovePeer(iface string, peerKey string) error {
|
||||
log.Debugf("Removing peer %s from interface %s ", peerKey, iface)
|
||||
|
||||
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
peer := wgtypes.PeerConfig{
|
||||
PublicKey: peerKeyParsed,
|
||||
Remove: true,
|
||||
}
|
||||
|
||||
config := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{peer},
|
||||
}
|
||||
|
||||
return configureDevice(iface, config)
|
||||
}
|
||||
|
||||
type wgLink struct {
|
||||
attrs *netlink.LinkAttrs
|
||||
}
|
||||
|
||||
func (w *wgLink) Attrs() *netlink.LinkAttrs {
|
||||
return w.attrs
|
||||
}
|
||||
|
||||
func (w *wgLink) Type() string {
|
||||
return "wireguard"
|
||||
// Closes the User Space tunnel interface
|
||||
func CloseWithUserspace() error {
|
||||
return tunIface.Close()
|
||||
}
|
||||
|
||||
@@ -3,24 +3,26 @@ package iface
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
interfacePrefix = "utun"
|
||||
)
|
||||
// Create Creates a new Wireguard interface, sets a given IP and brings it up.
|
||||
func Create(iface string, address string) error {
|
||||
return CreateWithUserspace(iface, address)
|
||||
}
|
||||
|
||||
// assignAddr Adds IP address to the tunnel interface and network route based on the range provided
|
||||
func assignAddr(iface string, address string) error {
|
||||
func assignAddr(address string, ifaceName string) error {
|
||||
ip := strings.Split(address, "/")
|
||||
cmd := exec.Command("ifconfig", iface, "inet", address, ip[0])
|
||||
cmd := exec.Command("ifconfig", ifaceName, "inet", address, ip[0])
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
log.Infoln("Command: %v failed with output %s and error: ", cmd.String(), out)
|
||||
log.Infof("Command: %v failed with output %s and error: ", cmd.String(), out)
|
||||
return err
|
||||
}
|
||||
_, resolvedNet, err := net.ParseCIDR(address)
|
||||
err = addRoute(iface, resolvedNet)
|
||||
err = addRoute(ifaceName, resolvedNet)
|
||||
if err != nil {
|
||||
log.Infoln("Adding route failed with error:", err)
|
||||
}
|
||||
@@ -36,3 +38,26 @@ func addRoute(iface string, ipNet *net.IPNet) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Closes the tunnel interface
|
||||
func Close() error {
|
||||
name, err := tunIface.Name()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sockPath := "/var/run/wireguard/" + name + ".sock"
|
||||
|
||||
err = CloseWithUserspace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(sockPath); err == nil {
|
||||
err = os.Remove(sockPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
interfacePrefix = "wg"
|
||||
)
|
||||
// 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 {
|
||||
|
||||
// assignAddr Adds IP address to the tunnel interface
|
||||
func assignAddr(iface string, address string) error {
|
||||
if WireguardModExists() {
|
||||
log.Debug("using kernel Wireguard module")
|
||||
return CreateWithKernel(iface, address)
|
||||
} else {
|
||||
return CreateWithUserspace(iface, address)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWithKernel Creates a new Wireguard interface using kernel Wireguard module.
|
||||
// Works for Linux and offers much better network performance
|
||||
func CreateWithKernel(iface string, address string) error {
|
||||
attrs := netlink.NewLinkAttrs()
|
||||
attrs.Name = iface
|
||||
|
||||
@@ -19,11 +30,85 @@ func assignAddr(iface string, address string) error {
|
||||
attrs: &attrs,
|
||||
}
|
||||
|
||||
log.Debugf("adding address %s to interface: %s", address, iface)
|
||||
addr, _ := netlink.ParseAddr(address)
|
||||
err := netlink.AddrAdd(&link, addr)
|
||||
// check if interface exists
|
||||
l, err := netlink.LinkByName(iface)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case netlink.LinkNotFoundError:
|
||||
break
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// remove if interface exists
|
||||
if l != nil {
|
||||
err = netlink.LinkDel(&link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("adding device: %s", iface)
|
||||
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.", iface)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = assignAddr(address, iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// todo do a discovery
|
||||
log.Debugf("setting MTU: %d interface: %s", defaultMTU, iface)
|
||||
err = netlink.LinkSetMTU(&link, defaultMTU)
|
||||
if err != nil {
|
||||
log.Errorf("error setting MTU on interface: %s", iface)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("bringing up interface: %s", iface)
|
||||
err = netlink.LinkSetUp(&link)
|
||||
if err != nil {
|
||||
log.Errorf("error bringing up interface: %s", iface)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// assignAddr Adds IP address to the tunnel interface
|
||||
func assignAddr(address, name string) error {
|
||||
var err error
|
||||
attrs := netlink.NewLinkAttrs()
|
||||
attrs.Name = name
|
||||
|
||||
link := wgLink{
|
||||
attrs: &attrs,
|
||||
}
|
||||
|
||||
//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, attrs.Name)
|
||||
addr, _ := netlink.ParseAddr(address)
|
||||
err = netlink.AddrAdd(&link, addr)
|
||||
if os.IsExist(err) {
|
||||
log.Infof("interface %s already has the address: %s", attrs.Name, address)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -31,3 +116,53 @@ func assignAddr(iface string, address string) error {
|
||||
err = netlink.LinkSetUp(&link)
|
||||
return err
|
||||
}
|
||||
|
||||
type wgLink struct {
|
||||
attrs *netlink.LinkAttrs
|
||||
}
|
||||
|
||||
// Attrs returns the Wireguard's default attributes
|
||||
func (w *wgLink) Attrs() *netlink.LinkAttrs {
|
||||
return w.attrs
|
||||
}
|
||||
|
||||
// Type returns the interface type
|
||||
func (w *wgLink) Type() string {
|
||||
return "wireguard"
|
||||
}
|
||||
|
||||
// Closes the tunnel interface
|
||||
func Close() error {
|
||||
|
||||
if tunIface != nil {
|
||||
return CloseWithUserspace()
|
||||
} else {
|
||||
var iface = ""
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wg.Close()
|
||||
devList, err := wg.Devices()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, wgDev := range devList {
|
||||
// todo check after move the WgPort constant to the client
|
||||
if wgDev.ListenPort == WgPort {
|
||||
iface = wgDev.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
if iface == "" {
|
||||
return fmt.Errorf("Wireguard Interface not found")
|
||||
}
|
||||
attrs := netlink.NewLinkAttrs()
|
||||
attrs.Name = iface
|
||||
|
||||
link := wgLink{
|
||||
attrs: &attrs,
|
||||
}
|
||||
return netlink.LinkDel(&link)
|
||||
}
|
||||
}
|
||||
|
||||
272
iface/iface_test.go
Normal file
@@ -0,0 +1,272 @@
|
||||
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 (
|
||||
key = "0PMI6OkB5JmB+Jj/iWWHekuQRx+bipZirWCWKFXexHc="
|
||||
peerPubKey = "Ok0mC0qlJyXEPKh2UFIpsI2jG0L7LRpC3sLAusSJ5CQ="
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
//
|
||||
func Test_CreateInterface(t *testing.T) {
|
||||
ifaceName := "utun999"
|
||||
wgIP := "10.99.99.1/24"
|
||||
err := Create(ifaceName, wgIP)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = 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)
|
||||
}
|
||||
}()
|
||||
|
||||
d, err := wg.Device(ifaceName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// todo move the WgPort constant to the client
|
||||
WgPort = d.ListenPort
|
||||
}
|
||||
func Test_ConfigureInterface(t *testing.T) {
|
||||
ifaceName := "utun1000"
|
||||
wgIP := "10.99.99.10/24"
|
||||
err := Create(ifaceName, wgIP)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = Configure(ifaceName, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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 := "utun1001"
|
||||
wgIP := "10.99.99.20/24"
|
||||
err := Create(ifaceName, wgIP)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
err = Configure(ifaceName, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keepAlive := 15 * time.Second
|
||||
allowedIP := "10.99.99.2/32"
|
||||
endpoint := "127.0.0.1:9900"
|
||||
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer, err := getPeer(ifaceName, t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if peer.PersistentKeepaliveInterval != keepAlive {
|
||||
t.Fatal("configured peer with mismatched keepalive interval value")
|
||||
}
|
||||
|
||||
resolvedEndpoint, err := net.ResolveUDPAddr("udp", endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if peer.Endpoint.String() != resolvedEndpoint.String() {
|
||||
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_UpdatePeerEndpoint(t *testing.T) {
|
||||
ifaceName := "utun1002"
|
||||
wgIP := "10.99.99.30/24"
|
||||
err := Create(ifaceName, wgIP)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
err = Configure(ifaceName, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keepAlive := 15 * time.Second
|
||||
allowedIP := "10.99.99.2/32"
|
||||
endpoint := "127.0.0.1:9900"
|
||||
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newEndpoint := "127.0.0.1:9999"
|
||||
err = UpdatePeerEndpoint(ifaceName, peerPubKey, newEndpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
peer, err := getPeer(ifaceName, t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if peer.Endpoint.String() != newEndpoint {
|
||||
t.Fatal("configured peer with mismatched endpoint")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RemovePeer(t *testing.T) {
|
||||
ifaceName := "utun1003"
|
||||
wgIP := "10.99.99.40/24"
|
||||
err := Create(ifaceName, wgIP)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
err = Configure(ifaceName, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keepAlive := 15 * time.Second
|
||||
allowedIP := "10.99.99.2/32"
|
||||
endpoint := "127.0.0.1:9900"
|
||||
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = RemovePeer(ifaceName, peerPubKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = getPeer(ifaceName, t)
|
||||
if err.Error() != "peer not found" {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
func Test_Close(t *testing.T) {
|
||||
ifaceName := "utun1004"
|
||||
wgIP := "10.99.99.50/24"
|
||||
err := Create(ifaceName, wgIP)
|
||||
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)
|
||||
}
|
||||
}()
|
||||
|
||||
d, err := wg.Device(ifaceName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// todo move the WgPort constant to the client
|
||||
WgPort = d.ListenPort
|
||||
err = Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
func getPeer(ifaceName 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")
|
||||
}
|
||||
17
iface/iface_unix.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// +build linux darwin
|
||||
|
||||
package iface
|
||||
|
||||
import (
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
"net"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
46
iface/iface_windows.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Create Creates a new Wireguard interface, sets a given IP and brings it up.
|
||||
func Create(iface string, address string) error {
|
||||
return CreateWithUserspace(iface, address)
|
||||
}
|
||||
|
||||
// assignAddr Adds IP address to the tunnel interface and network route based on the range provided
|
||||
func assignAddr(address string, ifaceName string) error {
|
||||
|
||||
nativeTunDevice := tunIface.(*tun.NativeTun)
|
||||
luid := winipcfg.LUID(nativeTunDevice.LUID())
|
||||
|
||||
ip, ipnet, _ := net.ParseCIDR(address)
|
||||
|
||||
log.Debugf("adding address %s to interface: %s", address, ifaceName)
|
||||
err := luid.SetIPAddresses([]net.IPNet{{ip, ipnet.Mask}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("adding Routes to interface: %s", ifaceName)
|
||||
err = luid.SetRoutes([]*winipcfg.RouteData{{*ipnet, ipnet.IP, 0}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getUAPI returns a Listener
|
||||
func getUAPI(iface string) (net.Listener, error) {
|
||||
return ipc.UAPIListen(iface)
|
||||
}
|
||||
|
||||
// Closes the tunnel interface
|
||||
func Close() error {
|
||||
return CloseWithUserspace()
|
||||
}
|
||||
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"
|
||||
144
iface/mod.go
Normal file
@@ -0,0 +1,144 @@
|
||||
// +build linux
|
||||
|
||||
package iface
|
||||
|
||||
// Holds logic to check existence of Wireguard kernel module
|
||||
// Copied from https://github.com/paultag/go-modprobe
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"fmt"
|
||||
"golang.org/x/sys/unix"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// get the root directory for the kernel modules. If this line panics,
|
||||
// it's because getModuleRoot has failed to get the uname of the running
|
||||
// kernel (likely a non-POSIX system, but maybe a broken kernel?)
|
||||
moduleRoot = getModuleRoot()
|
||||
)
|
||||
|
||||
// Get the module root (/lib/modules/$(uname -r)/)
|
||||
func getModuleRoot() string {
|
||||
uname := unix.Utsname{}
|
||||
if err := unix.Uname(&uname); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
i := 0
|
||||
for ; uname.Release[i] != 0; i++ {
|
||||
}
|
||||
|
||||
return filepath.Join(
|
||||
"/lib/modules",
|
||||
string(uname.Release[:i]),
|
||||
)
|
||||
}
|
||||
|
||||
// modName will, given a file descriptor to a Kernel Module (.ko file), parse the
|
||||
// binary to get the module name. For instance, given a handle to the file at
|
||||
// `kernel/drivers/usb/gadget/legacy/g_ether.ko`, return `g_ether`.
|
||||
func modName(file *os.File) (string, error) {
|
||||
f, err := elf.NewFile(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
syms, err := f.Symbols()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, sym := range syms {
|
||||
if strings.Compare(sym.Name, "__this_module") == 0 {
|
||||
section := f.Sections[sym.Section]
|
||||
data, err := section.Data()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(data) < 25 {
|
||||
return "", fmt.Errorf("modprobe: data is short, __this_module is '%s'", data)
|
||||
}
|
||||
|
||||
data = data[24:]
|
||||
i := 0
|
||||
for ; data[i] != 0x00; i++ {
|
||||
}
|
||||
return string(data[:i]), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("No name found. Is this a .ko or just an ELF?")
|
||||
}
|
||||
|
||||
// Open every single kernel module under the root, and parse the ELF headers to
|
||||
// extract the module name.
|
||||
func elfMap(root string) (map[string]string, error) {
|
||||
ret := map[string]string{}
|
||||
|
||||
err := filepath.Walk(
|
||||
root,
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
|
||||
if err != nil {
|
||||
// skip broken files
|
||||
return nil
|
||||
}
|
||||
|
||||
if !info.Mode().IsRegular() {
|
||||
return nil
|
||||
}
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
name, err := modName(fd)
|
||||
if err != nil {
|
||||
/* For now, let's just ignore that and avoid adding to it */
|
||||
return nil
|
||||
}
|
||||
|
||||
ret[name] = path
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Open every single kernel module under the kernel module directory
|
||||
// (/lib/modules/$(uname -r)/), and parse the ELF headers to extract the
|
||||
// module name.
|
||||
func generateMap() (map[string]string, error) {
|
||||
return elfMap(moduleRoot)
|
||||
}
|
||||
|
||||
// WireguardModExists returns true if Wireguard kernel module exists.
|
||||
func WireguardModExists() bool {
|
||||
_, err := resolveModName("wireguard")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// resolveModName will, given a module name (such as `wireguard`) return an absolute
|
||||
// path to the .ko that provides that module.
|
||||
func resolveModName(name string) (string, error) {
|
||||
paths, err := generateMap()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fsPath := paths[name]
|
||||
if !strings.HasPrefix(fsPath, moduleRoot) {
|
||||
return "", fmt.Errorf("module isn't in the module directory")
|
||||
}
|
||||
|
||||
return fsPath, nil
|
||||
}
|
||||
@@ -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
|
||||
62
infrastructure_files/docker-compose.yml.tmpl
Normal file
@@ -0,0 +1,62 @@
|
||||
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
|
||||
image: wiretrustee/management:v0.1.0-beta.2-SNAPSHOT-39d450b-amd64
|
||||
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:
|
||||
39
infrastructure_files/management.json.tmpl
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
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
|
||||
4
management/Dockerfile.debug
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM gcr.io/distroless/base:debug
|
||||
ENTRYPOINT [ "/go/bin/wiretrustee-mgmt","management","--log-level","debug"]
|
||||
CMD ["--log-file", "console"]
|
||||
COPY wiretrustee-mgmt /go/bin/wiretrustee-mgmt
|
||||
113
management/README.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Wiretrustee Management Server
|
||||
Wiretrustee management server will control and synchronize peers configuration within your wiretrustee account and network.
|
||||
|
||||
## Command Options
|
||||
The CLI accepts the command **management** with the following options:
|
||||
```shell
|
||||
start Wiretrustee Management Server
|
||||
|
||||
Usage:
|
||||
wiretrustee-mgmt management [flags]
|
||||
|
||||
Flags:
|
||||
--datadir string server data directory location (default "/var/lib/wiretrustee/")
|
||||
-h, --help help for management
|
||||
--letsencrypt-domain string a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS
|
||||
--port int server port to listen on (default 33073)
|
||||
--cert-file string Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect
|
||||
--cert-key string Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect
|
||||
Global Flags:
|
||||
--config string Wiretrustee config file location to write new config to (default "/etc/wiretrustee/config.json")
|
||||
--log-level string (default "info")
|
||||
--log-file string sets Wiretrustee log path. If console is specified the the log will be output to stdout (default "/var/log/wiretrustee/management.log")
|
||||
```
|
||||
## Run Management service (Docker)
|
||||
|
||||
You can run service in 2 modes - with TLS or without (not recommended).
|
||||
|
||||
### Run with TLS (Let's Encrypt).
|
||||
By specifying the **--letsencrypt-domain** the daemon will handle SSL certificate request and configuration.
|
||||
|
||||
In the following example ```33073``` is the management service **default** port, and ```443``` will be used as port for Let's Encrypt challenge and HTTP API.
|
||||
> The server where you are running a container has to have a public IP (for Let's Encrypt certificate challenge).
|
||||
|
||||
Replace <YOUR-DOMAIN> with your server's public domain (e.g. mydomain.com or subdomain sub.mydomain.com).
|
||||
|
||||
```bash
|
||||
# create a volume
|
||||
docker volume create wiretrustee-mgmt
|
||||
# run the docker container
|
||||
docker run -d --name wiretrustee-management \
|
||||
-p 33073:33073 \
|
||||
-p 443:443 \
|
||||
-v wiretrustee-mgmt:/var/lib/wiretrustee \
|
||||
-v ./config.json:/etc/wiretrustee/config.json \
|
||||
wiretrustee/management:latest \
|
||||
--letsencrypt-domain <YOUR-DOMAIN>
|
||||
```
|
||||
> An example of config.json can be found here [config.json](../infrastructure_files/config.json)
|
||||
|
||||
Trigger Let's encrypt certificate generation:
|
||||
```bash
|
||||
curl https://<YOUR-DOMAIN>
|
||||
```
|
||||
|
||||
The certificate will be persisted in the ```datadir/letsencrypt/``` folder (e.g. ```/var/lib/wiretrustee/letsencrypt/```) inside the container.
|
||||
|
||||
Make sure that the ```datadir``` is mapped to some folder on a host machine. In case you used the volume command, you can run the following to retrieve the Mountpoint:
|
||||
```shell
|
||||
docker volume inspect wiretrustee-mgmt
|
||||
[
|
||||
{
|
||||
"CreatedAt": "2021-07-25T20:45:28Z",
|
||||
"Driver": "local",
|
||||
"Labels": {},
|
||||
"Mountpoint": "/var/lib/docker/volumes/mgmt/_data",
|
||||
"Name": "wiretrustee-mgmt",
|
||||
"Options": {},
|
||||
"Scope": "local"
|
||||
}
|
||||
]
|
||||
```
|
||||
Consequent restarts of the container will pick up previously generated certificate so there is no need to trigger certificate generation with the ```curl``` command on every restart.
|
||||
|
||||
### Run without TLS.
|
||||
|
||||
```bash
|
||||
# create a volume
|
||||
docker volume create wiretrustee-mgmt
|
||||
# run the docker container
|
||||
docker run -d --name wiretrustee-management \
|
||||
-p 33073:33073 \
|
||||
-v wiretrustee-mgmt:/var/lib/wiretrustee \
|
||||
-v ./config.json:/etc/wiretrustee/config.json \
|
||||
wiretrustee/management:latest
|
||||
```
|
||||
### Debug tag
|
||||
We also publish a docker image with the debug tag which has the log-level set to default, plus it uses the ```gcr.io/distroless/base:debug``` image that can be used with docker exec in order to run some commands in the Management container.
|
||||
```shell
|
||||
shell $ docker run -d --name wiretrustee-management-debug \
|
||||
-p 33073:33073 \
|
||||
-v wiretrustee-mgmt:/var/lib/wiretrustee \
|
||||
-v ./config.json:/etc/wiretrustee/config.json \
|
||||
wiretrustee/management:debug-latest
|
||||
|
||||
shell $ docker exec -ti wiretrustee-management-debug /bin/sh
|
||||
container-shell $
|
||||
```
|
||||
## For development purposes:
|
||||
|
||||
Install golang gRpc tools:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
|
||||
```
|
||||
|
||||
Generate gRpc code:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
protoc -I proto/ proto/management.proto --go_out=. --go-grpc_out=.
|
||||
```
|
||||
|
||||
233
management/client/client.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/client/system"
|
||||
"github.com/wiretrustee/wiretrustee/encryption"
|
||||
"github.com/wiretrustee/wiretrustee/management/proto"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
key wgtypes.Key
|
||||
realClient proto.ManagementServiceClient
|
||||
ctx context.Context
|
||||
conn *grpc.ClientConn
|
||||
}
|
||||
|
||||
// NewClient creates a new client to Management service
|
||||
func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*Client, error) {
|
||||
|
||||
transportOption := grpc.WithInsecure()
|
||||
|
||||
if tlsEnabled {
|
||||
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
||||
}
|
||||
|
||||
mgmCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
conn, err := grpc.DialContext(
|
||||
mgmCtx,
|
||||
addr,
|
||||
transportOption,
|
||||
grpc.WithBlock(),
|
||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: 3 * time.Second,
|
||||
Timeout: 2 * time.Second,
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("failed creating connection to Management Srvice %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
realClient := proto.NewManagementServiceClient(conn)
|
||||
|
||||
return &Client{
|
||||
key: ourPrivateKey,
|
||||
realClient: realClient,
|
||||
ctx: ctx,
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close closes connection to the Management Service
|
||||
func (c *Client) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
//defaultBackoff is a basic backoff mechanism for general issues
|
||||
func defaultBackoff() backoff.BackOff {
|
||||
return &backoff.ExponentialBackOff{
|
||||
InitialInterval: 800 * time.Millisecond,
|
||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: 30 * time.Second,
|
||||
MaxElapsedTime: 24 * 3 * time.Hour, //stop after 3 days trying
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
}
|
||||
|
||||
// Sync wraps the real client's Sync endpoint call and takes care of retries and encryption/decryption of messages
|
||||
// Blocking request. The result will be sent via msgHandler callback function
|
||||
func (c *Client) Sync(msgHandler func(msg *proto.SyncResponse) error) error {
|
||||
|
||||
var backOff = defaultBackoff()
|
||||
|
||||
operation := func() error {
|
||||
|
||||
// todo we already have it since we did the Login, maybe cache it locally?
|
||||
serverPubKey, err := c.GetServerPublicKey()
|
||||
if err != nil {
|
||||
log.Errorf("failed getting Management Service public key: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
stream, err := c.connectToStream(*serverPubKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to open Management Service stream: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("connected to the Management Service Stream")
|
||||
|
||||
// blocking until error
|
||||
err = c.receiveEvents(stream, *serverPubKey, msgHandler)
|
||||
if err != nil {
|
||||
/*if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.PermissionDenied {
|
||||
//todo handle differently??
|
||||
}*/
|
||||
return err
|
||||
}
|
||||
backOff.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, backOff)
|
||||
if err != nil {
|
||||
log.Errorf("exiting Management Service connection retry loop due to unrecoverable error %s ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) connectToStream(serverPubKey wgtypes.Key) (proto.ManagementService_SyncClient, error) {
|
||||
req := &proto.SyncRequest{}
|
||||
|
||||
myPrivateKey := c.key
|
||||
myPublicKey := myPrivateKey.PublicKey()
|
||||
|
||||
encryptedReq, err := encryption.EncryptMessage(serverPubKey, myPrivateKey, req)
|
||||
if err != nil {
|
||||
log.Errorf("failed encrypting message: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
syncReq := &proto.EncryptedMessage{WgPubKey: myPublicKey.String(), Body: encryptedReq}
|
||||
return c.realClient.Sync(c.ctx, syncReq)
|
||||
}
|
||||
|
||||
func (c *Client) receiveEvents(stream proto.ManagementService_SyncClient, serverPubKey wgtypes.Key, msgHandler func(msg *proto.SyncResponse) error) error {
|
||||
for {
|
||||
update, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
log.Errorf("managment stream was closed: %s", err)
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("disconnected from Management Service sync stream: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("got an update message from Management Service")
|
||||
decryptedResp := &proto.SyncResponse{}
|
||||
err = encryption.DecryptMessage(serverPubKey, c.key, update.Body, decryptedResp)
|
||||
if err != nil {
|
||||
log.Errorf("failed decrypting update message from Management Service: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = msgHandler(decryptedResp)
|
||||
if err != nil {
|
||||
log.Errorf("failed handling an update message received from Management Service %v", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetServerPublicKey returns server Wireguard public key (used later for encrypting messages sent to the server)
|
||||
func (c *Client) GetServerPublicKey() (*wgtypes.Key, error) {
|
||||
mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second) //todo make a general setting
|
||||
defer cancel()
|
||||
resp, err := c.realClient.GetServerKey(mgmCtx, &proto.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverKey, err := wgtypes.ParseKey(resp.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &serverKey, nil
|
||||
}
|
||||
|
||||
func (c *Client) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*proto.LoginResponse, error) {
|
||||
loginReq, err := encryption.EncryptMessage(serverKey, c.key, req)
|
||||
if err != nil {
|
||||
log.Errorf("failed to encrypt message: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second) //todo make a general setting
|
||||
defer cancel()
|
||||
resp, err := c.realClient.Login(mgmCtx, &proto.EncryptedMessage{
|
||||
WgPubKey: c.key.PublicKey().String(),
|
||||
Body: loginReq,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loginResp := &proto.LoginResponse{}
|
||||
err = encryption.DecryptMessage(serverKey, c.key, resp.Body, loginResp)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt registration message: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return loginResp, nil
|
||||
}
|
||||
|
||||
// Register registers peer on Management Server. It actually calls a Login endpoint with a provided setup key
|
||||
// Takes care of encrypting and decrypting messages.
|
||||
// This method will also collect system info and send it with the request (e.g. hostname, os, etc)
|
||||
func (c *Client) Register(serverKey wgtypes.Key, setupKey string) (*proto.LoginResponse, error) {
|
||||
gi := system.GetInfo()
|
||||
meta := &proto.PeerSystemMeta{
|
||||
Hostname: gi.Hostname,
|
||||
GoOS: gi.GoOS,
|
||||
OS: gi.OS,
|
||||
Core: gi.OSVersion,
|
||||
Platform: gi.Platform,
|
||||
Kernel: gi.Kernel,
|
||||
WiretrusteeVersion: "",
|
||||
}
|
||||
log.Debugf("detected system %v", meta)
|
||||
return c.login(serverKey, &proto.LoginRequest{SetupKey: setupKey, Meta: meta})
|
||||
}
|
||||
|
||||
// Login attempts login to Management Server. Takes care of encrypting and decrypting messages.
|
||||
func (c *Client) Login(serverKey wgtypes.Key) (*proto.LoginResponse, error) {
|
||||
return c.login(serverKey, &proto.LoginRequest{})
|
||||
}
|
||||
179
management/client/client_test.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
log "github.com/sirupsen/logrus"
|
||||
mgmtProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var tested *Client
|
||||
var serverAddr string
|
||||
|
||||
const ValidKey = "A2C8E62B-38F5-4553-B31E-DD66C696CEBB"
|
||||
|
||||
func Test_Start(t *testing.T) {
|
||||
level, _ := log.ParseLevel("debug")
|
||||
log.SetLevel(level)
|
||||
|
||||
testKey, err := wgtypes.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testDir := t.TempDir()
|
||||
ctx := context.Background()
|
||||
config := &mgmt.Config{}
|
||||
_, err = util.ReadJson("../server/testdata/management.json", config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
config.Datadir = testDir
|
||||
err = util.CopyFileContents("../server/testdata/store.json", filepath.Join(testDir, "store.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, listener := startManagement(config, t)
|
||||
serverAddr = listener.Addr().String()
|
||||
tested, err = NewClient(ctx, serverAddr, testKey, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
func TestClient_GetServerPublicKey(t *testing.T) {
|
||||
|
||||
key, err := tested.GetServerPublicKey()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if key == nil {
|
||||
t.Error("expecting non nil server key got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_LoginUnregistered_ShouldThrow_401(t *testing.T) {
|
||||
key, err := tested.GetServerPublicKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = tested.Login(*key)
|
||||
if err == nil {
|
||||
t.Error("expecting err on unregistered login, got nil")
|
||||
}
|
||||
if s, ok := status.FromError(err); !ok || s.Code() != codes.PermissionDenied {
|
||||
t.Errorf("expecting err code %d denied on on unregistered login got %d", codes.PermissionDenied, s.Code())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_LoginRegistered(t *testing.T) {
|
||||
key, err := tested.GetServerPublicKey()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
resp, err := tested.Register(*key, ValidKey)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if resp == nil {
|
||||
t.Error("expecting non nil response, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Sync(t *testing.T) {
|
||||
serverKey, err := tested.GetServerPublicKey()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = tested.Register(*serverKey, ValidKey)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// create and register second peer (we should receive on Sync request)
|
||||
remoteKey, err := wgtypes.GenerateKey()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
remoteClient, err := NewClient(context.TODO(), serverAddr, remoteKey, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = remoteClient.Register(*serverKey, ValidKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ch := make(chan *mgmtProto.SyncResponse, 1)
|
||||
|
||||
go func() {
|
||||
err = tested.Sync(func(msg *mgmtProto.SyncResponse) error {
|
||||
ch <- msg
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case resp := <-ch:
|
||||
if resp.GetPeerConfig() == nil {
|
||||
t.Error("expecting non nil PeerConfig got nil")
|
||||
}
|
||||
if resp.GetWiretrusteeConfig() == nil {
|
||||
t.Error("expecting non nil WiretrusteeConfig got nil")
|
||||
}
|
||||
if len(resp.GetRemotePeers()) != 1 {
|
||||
t.Errorf("expecting RemotePeers size %d got %d", 1, len(resp.GetRemotePeers()))
|
||||
}
|
||||
if resp.GetRemotePeersIsEmpty() == true {
|
||||
t.Error("expecting RemotePeers property to be false, got true")
|
||||
}
|
||||
if resp.GetRemotePeers()[0].GetWgPubKey() != remoteKey.PublicKey().String() {
|
||||
t.Errorf("expecting RemotePeer public key %s got %s", remoteKey.PublicKey().String(), resp.GetRemotePeers()[0].GetWgPubKey())
|
||||
}
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Error("timeout waiting for test to finish")
|
||||
}
|
||||
}
|
||||
187
management/cmd/management.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/wiretrustee/wiretrustee/management/server"
|
||||
"github.com/wiretrustee/wiretrustee/management/server/http"
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wiretrustee/wiretrustee/encryption"
|
||||
mgmtProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
var (
|
||||
mgmtPort int
|
||||
mgmtDataDir string
|
||||
mgmtConfig string
|
||||
mgmtLetsencryptDomain string
|
||||
certFile string
|
||||
certKey string
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
mgmtCmd = &cobra.Command{
|
||||
Use: "management",
|
||||
Short: "start Wiretrustee Management Server",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
flag.Parse()
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
log.Fatalf("failed initializing log %v", err)
|
||||
}
|
||||
|
||||
config, err := loadConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("failed reading provided config file: %s: %v", mgmtConfig, err)
|
||||
}
|
||||
|
||||
if _, err = os.Stat(config.Datadir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(config.Datadir, os.ModeDir)
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating datadir: %s: %v", config.Datadir, err)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
var opts []grpc.ServerOption
|
||||
|
||||
var httpServer *http.Server
|
||||
if config.HttpConfig.LetsEncryptDomain != "" {
|
||||
//automatically generate a new certificate with Let's Encrypt
|
||||
certManager := encryption.CreateCertManager(config.Datadir, config.HttpConfig.LetsEncryptDomain)
|
||||
transportCredentials := credentials.NewTLS(certManager.TLSConfig())
|
||||
opts = append(opts, grpc.Creds(transportCredentials))
|
||||
|
||||
httpServer = http.NewHttpsServer(config.HttpConfig, certManager, accountManager)
|
||||
} else if config.HttpConfig.CertFile != "" && config.HttpConfig.CertKey != "" {
|
||||
//use provided certificate
|
||||
tlsConfig, err := loadTLSConfig(config.HttpConfig.CertFile, config.HttpConfig.CertKey)
|
||||
if err != nil {
|
||||
log.Fatal("cannot load TLS credentials: ", err)
|
||||
}
|
||||
transportCredentials := credentials.NewTLS(tlsConfig)
|
||||
opts = append(opts, grpc.Creds(transportCredentials))
|
||||
httpServer = http.NewHttpsServerWithTLSConfig(config.HttpConfig, tlsConfig, accountManager)
|
||||
} else {
|
||||
//start server without SSL
|
||||
httpServer = http.NewHttpServer(config.HttpConfig, accountManager)
|
||||
}
|
||||
|
||||
opts = append(opts, grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||
grpcServer := grpc.NewServer(opts...)
|
||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||
server, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating new server: %v", err)
|
||||
}
|
||||
mgmtProto.RegisterManagementServiceServer(grpcServer, server)
|
||||
log.Printf("started server: localhost:%v", mgmtPort)
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", mgmtPort))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err = grpcServer.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve gRpc server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
err = httpServer.Start()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to serve http server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
SetupCloseHandler()
|
||||
<-stopCh
|
||||
log.Println("Receive signal to stop running Management server")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
err = httpServer.Stop(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("failed stopping the http server %v", err)
|
||||
}
|
||||
|
||||
grpcServer.Stop()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func loadConfig() (*server.Config, error) {
|
||||
config := &server.Config{}
|
||||
_, err := util.ReadJson(mgmtConfig, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mgmtLetsencryptDomain != "" {
|
||||
config.HttpConfig.LetsEncryptDomain = mgmtLetsencryptDomain
|
||||
}
|
||||
if mgmtDataDir != "" {
|
||||
config.Datadir = mgmtDataDir
|
||||
}
|
||||
|
||||
if certKey != "" && certFile != "" {
|
||||
config.HttpConfig.CertFile = certFile
|
||||
config.HttpConfig.CertKey = certKey
|
||||
}
|
||||
|
||||
return config, err
|
||||
}
|
||||
|
||||
func loadTLSConfig(certFile string, certKey string) (*tls.Config, error) {
|
||||
// Load server's certificate and private key
|
||||
serverCert, err := tls.LoadX509KeyPair(certFile, certKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the credentials and return it
|
||||
config := &tls.Config{
|
||||
Certificates: []tls.Certificate{serverCert},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
mgmtCmd.Flags().IntVar(&mgmtPort, "port", 33073, "server port to listen on")
|
||||
mgmtCmd.Flags().StringVar(&mgmtDataDir, "datadir", "/var/lib/wiretrustee/", "server data directory location")
|
||||
mgmtCmd.Flags().StringVar(&mgmtConfig, "config", "/etc/wiretrustee/management.json", "Wiretrustee config file location. Config params specified via command line (e.g. datadir) have a precedence over configuration from this file")
|
||||
mgmtCmd.Flags().StringVar(&mgmtLetsencryptDomain, "letsencrypt-domain", "", "a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS")
|
||||
mgmtCmd.Flags().StringVar(&certFile, "cert-file", "", "Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
|
||||
mgmtCmd.Flags().StringVar(&certKey, "cert-key", "", "Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
|
||||
|
||||
rootCmd.MarkFlagRequired("config") //nolint
|
||||
|
||||
}
|
||||
63
management/cmd/root.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
// ExitSetupFailed defines exit code
|
||||
ExitSetupFailed = 1
|
||||
)
|
||||
|
||||
var (
|
||||
configPath string
|
||||
defaultConfigPath string
|
||||
logLevel string
|
||||
defaultLogFile string
|
||||
logFile string
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "wiretrustee-mgmt",
|
||||
Short: "",
|
||||
Long: "",
|
||||
}
|
||||
|
||||
// Execution control channel for stopCh signal
|
||||
stopCh chan int
|
||||
)
|
||||
|
||||
// Execute executes the root command.
|
||||
func Execute() error {
|
||||
return rootCmd.Execute()
|
||||
}
|
||||
func init() {
|
||||
|
||||
stopCh = make(chan int)
|
||||
|
||||
defaultConfigPath = "/etc/wiretrustee/management.json"
|
||||
defaultLogFile = "/var/log/wiretrustee/management.log"
|
||||
if runtime.GOOS == "windows" {
|
||||
defaultConfigPath = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "management.json"
|
||||
defaultLogFile = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "management.log"
|
||||
}
|
||||
rootCmd.PersistentFlags().StringVar(&configPath, "config", defaultConfigPath, "Wiretrustee config file location to write new config to")
|
||||
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
|
||||
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Wiretrustee log path. If console is specified the the log will be output to stdout")
|
||||
rootCmd.AddCommand(mgmtCmd)
|
||||
}
|
||||
|
||||
// SetupCloseHandler handles SIGTERM signal and exits with success
|
||||
func SetupCloseHandler() {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
for range c {
|
||||
fmt.Println("\r- Ctrl+C pressed in Terminal")
|
||||
stopCh <- 0
|
||||
}
|
||||
}()
|
||||
}
|
||||
12
management/main.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wiretrustee/wiretrustee/management/cmd"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := cmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
4
management/proto/generate.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
|
||||
protoc -I proto/ proto/management.proto --go_out=. --go-grpc_out=.
|
||||
1222
management/proto/management.pb.go
Normal file
134
management/proto/management.proto
Normal file
@@ -0,0 +1,134 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
option go_package = "/proto";
|
||||
|
||||
package management;
|
||||
|
||||
service ManagementService {
|
||||
|
||||
// Login logs in peer. In case server returns codes.PermissionDenied this endpoint can be used to register Peer providing LoginRequest.setupKey
|
||||
rpc Login(EncryptedMessage) returns (EncryptedMessage) {}
|
||||
|
||||
// Sync enables peer synchronization. Each peer that is connected to this stream will receive updates from the server.
|
||||
// For example, if a new peer has been added to an account all other connected peers will receive this peer's Wireguard public key as an update
|
||||
// The initial SyncResponse contains all of the available peers so the local state can be refreshed
|
||||
rpc Sync(EncryptedMessage) returns (stream EncryptedMessage) {}
|
||||
|
||||
// Exposes a Wireguard public key of the Management service.
|
||||
// This key is used to support message encryption between client and server
|
||||
rpc GetServerKey(Empty) returns (ServerKeyResponse) {}
|
||||
|
||||
// health check endpoint
|
||||
rpc isHealthy(Empty) returns (Empty) {}
|
||||
}
|
||||
|
||||
message EncryptedMessage {
|
||||
// Wireguard public key
|
||||
string wgPubKey = 1;
|
||||
|
||||
// encrypted message Body
|
||||
bytes body = 2;
|
||||
}
|
||||
|
||||
message SyncRequest {}
|
||||
|
||||
// SyncResponse represents a state that should be applied to the local peer (e.g. Wiretrustee servers config as well as local peer and remote peers configs)
|
||||
message SyncResponse {
|
||||
// Global config
|
||||
WiretrusteeConfig wiretrusteeConfig = 1;
|
||||
|
||||
PeerConfig peerConfig = 2;
|
||||
|
||||
repeated RemotePeerConfig remotePeers = 3;
|
||||
// Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
|
||||
bool remotePeersIsEmpty = 4;
|
||||
}
|
||||
|
||||
message LoginRequest {
|
||||
// Pre-authorized setup key (can be empty)
|
||||
string setupKey = 1;
|
||||
// Meta data of the peer (e.g. name, os_name, os_version,
|
||||
PeerSystemMeta meta = 2;
|
||||
}
|
||||
|
||||
// Peer machine meta data
|
||||
message PeerSystemMeta {
|
||||
string hostname = 1;
|
||||
string goOS = 2;
|
||||
string kernel = 3;
|
||||
string core = 4;
|
||||
string platform = 5;
|
||||
string OS = 6;
|
||||
string wiretrusteeVersion = 7;
|
||||
}
|
||||
|
||||
message LoginResponse {
|
||||
// Global config
|
||||
WiretrusteeConfig wiretrusteeConfig = 1;
|
||||
// Peer local config
|
||||
PeerConfig peerConfig = 2;
|
||||
}
|
||||
|
||||
message ServerKeyResponse {
|
||||
// Server's Wireguard public key
|
||||
string key = 1;
|
||||
// Key expiration timestamp after which the key should be fetched again by the client
|
||||
google.protobuf.Timestamp expiresAt = 2;
|
||||
}
|
||||
|
||||
message Empty {}
|
||||
|
||||
// WiretrusteeConfig is a common configuration of any Wiretrustee peer. It contains STUN, TURN, Signal and Management servers configurations
|
||||
message WiretrusteeConfig {
|
||||
// a list of STUN servers
|
||||
repeated HostConfig stuns = 1;
|
||||
// a list of TURN servers
|
||||
repeated ProtectedHostConfig turns = 2;
|
||||
|
||||
// a Signal server config
|
||||
HostConfig signal = 3;
|
||||
}
|
||||
|
||||
// HostConfig describes connection properties of some server (e.g. STUN, Signal, Management)
|
||||
message HostConfig {
|
||||
// URI of the resource e.g. turns://stun.wiretrustee.com:4430 or signal.wiretrustee.com:10000
|
||||
string uri = 1;
|
||||
Protocol protocol = 2;
|
||||
|
||||
enum Protocol {
|
||||
UDP = 0;
|
||||
TCP = 1;
|
||||
HTTP = 2;
|
||||
HTTPS = 3;
|
||||
DTLS = 4;
|
||||
}
|
||||
}
|
||||
// ProtectedHostConfig is similar to HostConfig but has additional user and password
|
||||
// Mostly used for TURN servers
|
||||
message ProtectedHostConfig {
|
||||
HostConfig hostConfig = 1;
|
||||
string user = 2;
|
||||
string password = 3;
|
||||
}
|
||||
|
||||
// PeerConfig represents a configuration of a "our" peer.
|
||||
// The properties are used to configure local Wireguard
|
||||
message PeerConfig {
|
||||
// Peer's virtual IP address within the Wiretrustee VPN (a Wireguard address config)
|
||||
string address = 1;
|
||||
// Wiretrustee DNS server (a Wireguard DNS config)
|
||||
string dns = 2;
|
||||
}
|
||||
|
||||
// RemotePeerConfig represents a configuration of a remote peer.
|
||||
// The properties are used to configure Wireguard Peers sections
|
||||
message RemotePeerConfig {
|
||||
|
||||
// A Wireguard public key of a remote peer
|
||||
string wgPubKey = 1;
|
||||
|
||||
// Wireguard allowed IPs of a remote peer e.g. [10.30.30.1/32]
|
||||
repeated string allowedIps = 2;
|
||||
}
|
||||
251
management/proto/management_grpc.pb.go
Normal file
@@ -0,0 +1,251 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// ManagementServiceClient is the client API for ManagementService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type ManagementServiceClient interface {
|
||||
// Login logs in peer. In case server returns codes.PermissionDenied this endpoint can be used to register Peer providing LoginRequest.setupKey
|
||||
Login(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error)
|
||||
// Sync enables peer synchronization. Each peer that is connected to this stream will receive updates from the server.
|
||||
// For example, if a new peer has been added to an account all other connected peers will receive this peer's Wireguard public key as an update
|
||||
// The initial SyncResponse contains all of the available peers so the local state can be refreshed
|
||||
Sync(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (ManagementService_SyncClient, error)
|
||||
// Exposes a Wireguard public key of the Management service.
|
||||
// This key is used to support message encryption between client and server
|
||||
GetServerKey(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ServerKeyResponse, error)
|
||||
// health check endpoint
|
||||
IsHealthy(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error)
|
||||
}
|
||||
|
||||
type managementServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewManagementServiceClient(cc grpc.ClientConnInterface) ManagementServiceClient {
|
||||
return &managementServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *managementServiceClient) Login(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) {
|
||||
out := new(EncryptedMessage)
|
||||
err := c.cc.Invoke(ctx, "/management.ManagementService/Login", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *managementServiceClient) Sync(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (ManagementService_SyncClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &ManagementService_ServiceDesc.Streams[0], "/management.ManagementService/Sync", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &managementServiceSyncClient{stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type ManagementService_SyncClient interface {
|
||||
Recv() (*EncryptedMessage, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type managementServiceSyncClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *managementServiceSyncClient) Recv() (*EncryptedMessage, error) {
|
||||
m := new(EncryptedMessage)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *managementServiceClient) GetServerKey(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ServerKeyResponse, error) {
|
||||
out := new(ServerKeyResponse)
|
||||
err := c.cc.Invoke(ctx, "/management.ManagementService/GetServerKey", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *managementServiceClient) IsHealthy(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := c.cc.Invoke(ctx, "/management.ManagementService/isHealthy", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ManagementServiceServer is the server API for ManagementService service.
|
||||
// All implementations must embed UnimplementedManagementServiceServer
|
||||
// for forward compatibility
|
||||
type ManagementServiceServer interface {
|
||||
// Login logs in peer. In case server returns codes.PermissionDenied this endpoint can be used to register Peer providing LoginRequest.setupKey
|
||||
Login(context.Context, *EncryptedMessage) (*EncryptedMessage, error)
|
||||
// Sync enables peer synchronization. Each peer that is connected to this stream will receive updates from the server.
|
||||
// For example, if a new peer has been added to an account all other connected peers will receive this peer's Wireguard public key as an update
|
||||
// The initial SyncResponse contains all of the available peers so the local state can be refreshed
|
||||
Sync(*EncryptedMessage, ManagementService_SyncServer) error
|
||||
// Exposes a Wireguard public key of the Management service.
|
||||
// This key is used to support message encryption between client and server
|
||||
GetServerKey(context.Context, *Empty) (*ServerKeyResponse, error)
|
||||
// health check endpoint
|
||||
IsHealthy(context.Context, *Empty) (*Empty, error)
|
||||
mustEmbedUnimplementedManagementServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedManagementServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedManagementServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedManagementServiceServer) Login(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Login not implemented")
|
||||
}
|
||||
func (UnimplementedManagementServiceServer) Sync(*EncryptedMessage, ManagementService_SyncServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method Sync not implemented")
|
||||
}
|
||||
func (UnimplementedManagementServiceServer) GetServerKey(context.Context, *Empty) (*ServerKeyResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetServerKey not implemented")
|
||||
}
|
||||
func (UnimplementedManagementServiceServer) IsHealthy(context.Context, *Empty) (*Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method IsHealthy not implemented")
|
||||
}
|
||||
func (UnimplementedManagementServiceServer) mustEmbedUnimplementedManagementServiceServer() {}
|
||||
|
||||
// UnsafeManagementServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to ManagementServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeManagementServiceServer interface {
|
||||
mustEmbedUnimplementedManagementServiceServer()
|
||||
}
|
||||
|
||||
func RegisterManagementServiceServer(s grpc.ServiceRegistrar, srv ManagementServiceServer) {
|
||||
s.RegisterService(&ManagementService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _ManagementService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(EncryptedMessage)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ManagementServiceServer).Login(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/management.ManagementService/Login",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ManagementServiceServer).Login(ctx, req.(*EncryptedMessage))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ManagementService_Sync_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(EncryptedMessage)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(ManagementServiceServer).Sync(m, &managementServiceSyncServer{stream})
|
||||
}
|
||||
|
||||
type ManagementService_SyncServer interface {
|
||||
Send(*EncryptedMessage) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type managementServiceSyncServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *managementServiceSyncServer) Send(m *EncryptedMessage) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _ManagementService_GetServerKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ManagementServiceServer).GetServerKey(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/management.ManagementService/GetServerKey",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ManagementServiceServer).GetServerKey(ctx, req.(*Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ManagementService_IsHealthy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ManagementServiceServer).IsHealthy(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/management.ManagementService/isHealthy",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ManagementServiceServer).IsHealthy(ctx, req.(*Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// ManagementService_ServiceDesc is the grpc.ServiceDesc for ManagementService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var ManagementService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "management.ManagementService",
|
||||
HandlerType: (*ManagementServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Login",
|
||||
Handler: _ManagementService_Login_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetServerKey",
|
||||
Handler: _ManagementService_GetServerKey_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "isHealthy",
|
||||
Handler: _ManagementService_IsHealthy_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Sync",
|
||||
Handler: _ManagementService_Sync_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "management.proto",
|
||||
}
|
||||
230
management/server/account.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AccountManager struct {
|
||||
Store Store
|
||||
// mutex to synchronise account operations (e.g. generating Peer IP address inside the Network)
|
||||
mux sync.Mutex
|
||||
peersUpdateManager *PeersUpdateManager
|
||||
}
|
||||
|
||||
// Account represents a unique account of the system
|
||||
type Account struct {
|
||||
Id string
|
||||
SetupKeys map[string]*SetupKey
|
||||
Network *Network
|
||||
Peers map[string]*Peer
|
||||
}
|
||||
|
||||
// NewManager creates a new AccountManager with a provided Store
|
||||
func NewManager(store Store, peersUpdateManager *PeersUpdateManager) *AccountManager {
|
||||
return &AccountManager{
|
||||
Store: store,
|
||||
mux: sync.Mutex{},
|
||||
peersUpdateManager: peersUpdateManager,
|
||||
}
|
||||
}
|
||||
|
||||
//AddSetupKey generates a new setup key with a given name and type, and adds it to the specified account
|
||||
func (am *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn time.Duration) (*SetupKey, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||
}
|
||||
|
||||
setupKey := GenerateSetupKey(keyName, keyType, expiresIn)
|
||||
account.SetupKeys[setupKey.Key] = setupKey
|
||||
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed adding account key")
|
||||
}
|
||||
|
||||
return setupKey, nil
|
||||
}
|
||||
|
||||
//RevokeSetupKey marks SetupKey as revoked - becomes not valid anymore
|
||||
func (am *AccountManager) RevokeSetupKey(accountId string, keyId string) (*SetupKey, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||
}
|
||||
|
||||
setupKey := getAccountSetupKeyById(account, keyId)
|
||||
if setupKey == nil {
|
||||
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", keyId)
|
||||
}
|
||||
|
||||
keyCopy := setupKey.Copy()
|
||||
keyCopy.Revoked = true
|
||||
account.SetupKeys[keyCopy.Key] = keyCopy
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed adding account key")
|
||||
}
|
||||
|
||||
return keyCopy, nil
|
||||
}
|
||||
|
||||
//RenameSetupKey renames existing setup key of the specified account.
|
||||
func (am *AccountManager) RenameSetupKey(accountId string, keyId string, newName string) (*SetupKey, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||
}
|
||||
|
||||
setupKey := getAccountSetupKeyById(account, keyId)
|
||||
if setupKey == nil {
|
||||
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", keyId)
|
||||
}
|
||||
|
||||
keyCopy := setupKey.Copy()
|
||||
keyCopy.Name = newName
|
||||
account.SetupKeys[keyCopy.Key] = keyCopy
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed adding account key")
|
||||
}
|
||||
|
||||
return keyCopy, nil
|
||||
}
|
||||
|
||||
//GetAccount returns an existing account or error (NotFound) if doesn't exist
|
||||
func (am *AccountManager) GetAccount(accountId string) (*Account, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// GetOrCreateAccount returns an existing account or creates a new one if doesn't exist
|
||||
func (am *AccountManager) GetOrCreateAccount(accountId string) (*Account, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
_, err := am.Store.GetAccount(accountId)
|
||||
if err != nil {
|
||||
if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
|
||||
return am.createAccount(accountId)
|
||||
} else {
|
||||
// other error
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
account, err := am.Store.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed retrieving account")
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
//AccountExists checks whether account exists (returns true) or not (returns false)
|
||||
func (am *AccountManager) AccountExists(accountId string) (*bool, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
var res bool
|
||||
_, err := am.Store.GetAccount(accountId)
|
||||
if err != nil {
|
||||
if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
|
||||
res = false
|
||||
return &res, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
res = true
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// AddAccount generates a new Account with a provided accountId and saves to the Store
|
||||
func (am *AccountManager) AddAccount(accountId string) (*Account, error) {
|
||||
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
return am.createAccount(accountId)
|
||||
|
||||
}
|
||||
|
||||
func (am *AccountManager) createAccount(accountId string) (*Account, error) {
|
||||
account, _ := newAccountWithId(accountId)
|
||||
|
||||
err := am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed creating account")
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// newAccountWithId creates a new Account with a default SetupKey (doesn't store in a Store) and provided id
|
||||
func newAccountWithId(accountId string) (*Account, *SetupKey) {
|
||||
|
||||
log.Debugf("creating new account")
|
||||
|
||||
setupKeys := make(map[string]*SetupKey)
|
||||
defaultKey := GenerateDefaultSetupKey()
|
||||
oneOffKey := GenerateSetupKey("One-off key", SetupKeyOneOff, DefaultSetupKeyDuration)
|
||||
setupKeys[defaultKey.Key] = defaultKey
|
||||
setupKeys[oneOffKey.Key] = oneOffKey
|
||||
network := &Network{
|
||||
Id: uuid.New().String(),
|
||||
Net: net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 192, 0, 0}},
|
||||
Dns: ""}
|
||||
peers := make(map[string]*Peer)
|
||||
|
||||
log.Debugf("created new account %s with setup key %s", accountId, defaultKey.Key)
|
||||
|
||||
return &Account{Id: accountId, SetupKeys: setupKeys, Network: network, Peers: peers}, defaultKey
|
||||
}
|
||||
|
||||
// newAccount creates a new Account with a default SetupKey (doesn't store in a Store)
|
||||
func newAccount() (*Account, *SetupKey) {
|
||||
accountId := uuid.New().String()
|
||||
return newAccountWithId(accountId)
|
||||
}
|
||||
|
||||
func getAccountSetupKeyById(acc *Account, keyId string) *SetupKey {
|
||||
for _, k := range acc.SetupKeys {
|
||||
if keyId == k.Id {
|
||||
return k
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAccountSetupKeyByKey(acc *Account, key string) *SetupKey {
|
||||
for _, k := range acc.SetupKeys {
|
||||
if key == k.Key {
|
||||
return k
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
215
management/server/account_test.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAccountManager_AddAccount(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
expectedId := "test_account"
|
||||
expectedPeersSize := 0
|
||||
expectedSetupKeysSize := 2
|
||||
expectedNetwork := net.IPNet{
|
||||
IP: net.IP{100, 64, 0, 0},
|
||||
Mask: net.IPMask{255, 192, 0, 0},
|
||||
}
|
||||
|
||||
account, err := manager.AddAccount(expectedId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if account.Id != expectedId {
|
||||
t.Errorf("expected account to have Id = %s, got %s", expectedId, account.Id)
|
||||
}
|
||||
|
||||
if len(account.Peers) != expectedPeersSize {
|
||||
t.Errorf("expected account to have len(Peers) = %v, got %v", expectedPeersSize, len(account.Peers))
|
||||
}
|
||||
|
||||
if len(account.SetupKeys) != expectedSetupKeysSize {
|
||||
t.Errorf("expected account to have len(SetupKeys) = %v, got %v", expectedSetupKeysSize, len(account.SetupKeys))
|
||||
}
|
||||
|
||||
if account.Network.Net.String() != expectedNetwork.String() {
|
||||
t.Errorf("expected account to have Network = %v, got %v", expectedNetwork.String(), account.Network.Net.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountManager_GetOrCreateAccount(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
expectedId := "test_account"
|
||||
|
||||
//make sure account doesn't exist
|
||||
account, err := manager.GetAccount(expectedId)
|
||||
if err != nil {
|
||||
errStatus, ok := status.FromError(err)
|
||||
if !(ok && errStatus.Code() == codes.NotFound) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if account != nil {
|
||||
t.Fatal("expecting empty account")
|
||||
}
|
||||
|
||||
account, err = manager.GetOrCreateAccount(expectedId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if account.Id != expectedId {
|
||||
t.Fatalf("expected to create an account, got wrong account")
|
||||
}
|
||||
|
||||
account, err = manager.GetOrCreateAccount(expectedId)
|
||||
if err != nil {
|
||||
t.Errorf("expected to get existing account after creation, failed")
|
||||
}
|
||||
|
||||
if account.Id != expectedId {
|
||||
t.Fatalf("expected to create an account, got wrong account")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountManager_AccountExists(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
expectedId := "test_account"
|
||||
_, err = manager.AddAccount(expectedId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
exists, err := manager.AccountExists(expectedId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !*exists {
|
||||
t.Errorf("expected account to exist after creation, got false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAccountManager_GetAccount(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
expectedId := "test_account"
|
||||
account, err := manager.AddAccount(expectedId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//AddAccount has been already tested so we can assume it is correct and compare results
|
||||
getAccount, err := manager.GetAccount(expectedId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if account.Id != getAccount.Id {
|
||||
t.Errorf("expected account.Id %s, got %s", account.Id, getAccount.Id)
|
||||
}
|
||||
|
||||
for _, peer := range account.Peers {
|
||||
if _, ok := getAccount.Peers[peer.Key]; !ok {
|
||||
t.Errorf("expected account to have peer %s, not found", peer.Key)
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range account.SetupKeys {
|
||||
if _, ok := getAccount.SetupKeys[key.Key]; !ok {
|
||||
t.Errorf("expected account to have setup key %s, not found", key.Key)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAccountManager_AddPeer(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
account, err := manager.AddAccount("test_account")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var setupKey *SetupKey
|
||||
for _, key := range account.SetupKeys {
|
||||
setupKey = key
|
||||
}
|
||||
|
||||
if setupKey == nil {
|
||||
t.Errorf("expecting account to have a default setup key")
|
||||
return
|
||||
}
|
||||
|
||||
key, err := wgtypes.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
expectedPeerKey := key.PublicKey().String()
|
||||
expectedPeerIP := "100.64.0.1"
|
||||
|
||||
peer, err := manager.AddPeer(setupKey.Key, Peer{
|
||||
Key: expectedPeerKey,
|
||||
Meta: PeerSystemMeta{},
|
||||
Name: expectedPeerKey,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("expecting peer to be added, got failure %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if peer.Key != expectedPeerKey {
|
||||
t.Errorf("expecting just added peer to have key = %s, got %s", expectedPeerKey, peer.Key)
|
||||
}
|
||||
|
||||
if peer.Key != expectedPeerKey {
|
||||
t.Errorf("expecting just added peer to have IP = %s, got %s", expectedPeerIP, peer.IP.String())
|
||||
}
|
||||
|
||||
}
|
||||
func createManager(t *testing.T) (*AccountManager, error) {
|
||||
store, err := createStore(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewManager(store, NewPeersUpdateManager()), nil
|
||||
}
|
||||
|
||||
func createStore(t *testing.T) (Store, error) {
|
||||
dataDir := t.TempDir()
|
||||
store, err := NewStore(dataDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return store, nil
|
||||
}
|
||||
59
management/server/config.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
)
|
||||
|
||||
type Protocol string
|
||||
|
||||
const (
|
||||
UDP Protocol = "udp"
|
||||
DTLS Protocol = "dtls"
|
||||
TCP Protocol = "tcp"
|
||||
HTTP Protocol = "http"
|
||||
HTTPS Protocol = "https"
|
||||
)
|
||||
|
||||
// Config of the Management service
|
||||
type Config struct {
|
||||
Stuns []*Host
|
||||
TURNConfig *TURNConfig
|
||||
Signal *Host
|
||||
|
||||
Datadir string
|
||||
|
||||
HttpConfig *HttpServerConfig
|
||||
}
|
||||
|
||||
// TURNConfig is a config of the TURNCredentialsManager
|
||||
type TURNConfig struct {
|
||||
TimeBasedCredentials bool
|
||||
CredentialsTTL util.Duration
|
||||
Secret string
|
||||
Turns []*Host
|
||||
}
|
||||
|
||||
// HttpServerConfig is a config of the HTTP Management service server
|
||||
type HttpServerConfig struct {
|
||||
LetsEncryptDomain string
|
||||
//CertFile is the location of the certificate
|
||||
CertFile string
|
||||
//CertKey is the location of the certificate private key
|
||||
CertKey string
|
||||
Address string
|
||||
// AuthAudience identifies the recipients that the JWT is intended for (aud in JWT)
|
||||
AuthAudience string
|
||||
// AuthIssuer identifies principal that issued the JWT.
|
||||
AuthIssuer string
|
||||
// AuthKeysLocation is a location of JWT key set containing the public keys used to verify JWT
|
||||
AuthKeysLocation string
|
||||
}
|
||||
|
||||
// Host represents a Wiretrustee host (e.g. STUN, TURN, Signal)
|
||||
type Host struct {
|
||||
Proto Protocol
|
||||
// URI e.g. turns://stun.wiretrustee.com:4430 or signal.wiretrustee.com:10000
|
||||
URI string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
230
management/server/file_store.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
)
|
||||
|
||||
// storeFileName Store file name. Stored in the datadir
|
||||
const storeFileName = "store.json"
|
||||
|
||||
// FileStore represents an account storage backed by a file persisted to disk
|
||||
type FileStore struct {
|
||||
Accounts map[string]*Account
|
||||
SetupKeyId2AccountId map[string]string `json:"-"`
|
||||
PeerKeyId2AccountId map[string]string `json:"-"`
|
||||
|
||||
// mutex to synchronise Store read/write operations
|
||||
mux sync.Mutex `json:"-"`
|
||||
storeFile string `json:"-"`
|
||||
}
|
||||
|
||||
type StoredAccount struct {
|
||||
}
|
||||
|
||||
// NewStore restores a store from the file located in the datadir
|
||||
func NewStore(dataDir string) (*FileStore, error) {
|
||||
return restore(filepath.Join(dataDir, storeFileName))
|
||||
}
|
||||
|
||||
// restore restores the state of the store from the file.
|
||||
// Creates a new empty store file if doesn't exist
|
||||
func restore(file string) (*FileStore, error) {
|
||||
|
||||
if _, err := os.Stat(file); os.IsNotExist(err) {
|
||||
// create a new FileStore if previously didn't exist (e.g. first run)
|
||||
s := &FileStore{
|
||||
Accounts: make(map[string]*Account),
|
||||
mux: sync.Mutex{},
|
||||
SetupKeyId2AccountId: make(map[string]string),
|
||||
PeerKeyId2AccountId: make(map[string]string),
|
||||
storeFile: file,
|
||||
}
|
||||
|
||||
err = s.persist(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
read, err := util.ReadJson(file, &FileStore{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store := read.(*FileStore)
|
||||
store.storeFile = file
|
||||
store.SetupKeyId2AccountId = make(map[string]string)
|
||||
store.PeerKeyId2AccountId = make(map[string]string)
|
||||
for accountId, account := range store.Accounts {
|
||||
for setupKeyId := range account.SetupKeys {
|
||||
store.SetupKeyId2AccountId[strings.ToUpper(setupKeyId)] = accountId
|
||||
}
|
||||
for _, peer := range account.Peers {
|
||||
store.PeerKeyId2AccountId[peer.Key] = accountId
|
||||
}
|
||||
}
|
||||
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// persist persists account data to a file
|
||||
// It is recommended to call it with locking FileStore.mux
|
||||
func (s *FileStore) persist(file string) error {
|
||||
return util.WriteJson(file, s)
|
||||
}
|
||||
|
||||
// SavePeer saves updated peer
|
||||
func (s *FileStore) SavePeer(accountId string, peer *Peer) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
account, err := s.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account.Peers[peer.Key] = peer
|
||||
err = s.persist(s.storeFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeletePeer deletes peer from the Store
|
||||
func (s *FileStore) DeletePeer(accountId string, peerKey string) (*Peer, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
account, err := s.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer := account.Peers[peerKey]
|
||||
if peer == nil {
|
||||
return nil, status.Errorf(codes.NotFound, "peer not found")
|
||||
}
|
||||
|
||||
delete(account.Peers, peerKey)
|
||||
delete(s.PeerKeyId2AccountId, peerKey)
|
||||
|
||||
err = s.persist(s.storeFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return peer, err
|
||||
}
|
||||
|
||||
// GetPeer returns a peer from a Store
|
||||
func (s *FileStore) GetPeer(peerKey string) (*Peer, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
accountId, accountIdFound := s.PeerKeyId2AccountId[peerKey]
|
||||
if !accountIdFound {
|
||||
return nil, status.Errorf(codes.NotFound, "peer not found")
|
||||
}
|
||||
|
||||
account, err := s.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if peer, ok := account.Peers[peerKey]; ok {
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
return nil, status.Errorf(codes.NotFound, "peer not found")
|
||||
}
|
||||
|
||||
// SaveAccount updates an existing account or adds a new one
|
||||
func (s *FileStore) SaveAccount(account *Account) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
// todo will override, handle existing keys
|
||||
s.Accounts[account.Id] = account
|
||||
|
||||
// todo check that account.Id and keyId are not exist already
|
||||
// because if keyId exists for other accounts this can be bad
|
||||
for keyId := range account.SetupKeys {
|
||||
s.SetupKeyId2AccountId[strings.ToUpper(keyId)] = account.Id
|
||||
}
|
||||
|
||||
for _, peer := range account.Peers {
|
||||
s.PeerKeyId2AccountId[peer.Key] = account.Id
|
||||
}
|
||||
|
||||
err := s.persist(s.storeFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
||||
|
||||
accountId, accountIdFound := s.SetupKeyId2AccountId[strings.ToUpper(setupKey)]
|
||||
if !accountIdFound {
|
||||
return nil, status.Errorf(codes.NotFound, "provided setup key doesn't exists")
|
||||
}
|
||||
|
||||
account, err := s.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
func (s *FileStore) GetAccountPeers(accountId string) ([]*Peer, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
account, err := s.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var peers []*Peer
|
||||
for _, peer := range account.Peers {
|
||||
peers = append(peers, peer)
|
||||
}
|
||||
|
||||
return peers, nil
|
||||
}
|
||||
|
||||
func (s *FileStore) GetAccount(accountId string) (*Account, error) {
|
||||
|
||||
account, accountFound := s.Accounts[accountId]
|
||||
if !accountFound {
|
||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (s *FileStore) GetPeerAccount(peerKey string) (*Account, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
accountId, accountIdFound := s.PeerKeyId2AccountId[peerKey]
|
||||
if !accountIdFound {
|
||||
return nil, status.Errorf(codes.NotFound, "Provided peer key doesn't exists %s", peerKey)
|
||||
}
|
||||
|
||||
return s.GetAccount(accountId)
|
||||
}
|
||||
376
management/server/grpcserver.go
Normal file
@@ -0,0 +1,376 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/encryption"
|
||||
"github.com/wiretrustee/wiretrustee/management/proto"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Server an instance of a Management server
|
||||
type Server struct {
|
||||
accountManager *AccountManager
|
||||
wgKey wgtypes.Key
|
||||
proto.UnimplementedManagementServiceServer
|
||||
peersUpdateManager *PeersUpdateManager
|
||||
config *Config
|
||||
turnCredentialsManager TURNCredentialsManager
|
||||
}
|
||||
|
||||
// AllowedIPsFormat generates Wireguard AllowedIPs format (e.g. 100.30.30.1/32)
|
||||
const AllowedIPsFormat = "%s/32"
|
||||
|
||||
// NewServer creates a new Management server
|
||||
func NewServer(config *Config, accountManager *AccountManager, peersUpdateManager *PeersUpdateManager, turnCredentialsManager TURNCredentialsManager) (*Server, error) {
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Server{
|
||||
wgKey: key,
|
||||
// peerKey -> event channel
|
||||
peersUpdateManager: peersUpdateManager,
|
||||
accountManager: accountManager,
|
||||
config: config,
|
||||
turnCredentialsManager: turnCredentialsManager,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetServerKey(ctx context.Context, req *proto.Empty) (*proto.ServerKeyResponse, error) {
|
||||
|
||||
// todo introduce something more meaningful with the key expiration/rotation
|
||||
now := time.Now().Add(24 * time.Hour)
|
||||
secs := int64(now.Second())
|
||||
nanos := int32(now.Nanosecond())
|
||||
expiresAt := ×tamp.Timestamp{Seconds: secs, Nanos: nanos}
|
||||
|
||||
return &proto.ServerKeyResponse{
|
||||
Key: s.wgKey.PublicKey().String(),
|
||||
ExpiresAt: expiresAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//Sync validates the existence of a connecting peer, sends an initial state (all available for the connecting peers) and
|
||||
// notifies the connected peer of any updates (e.g. new peers under the same account)
|
||||
func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_SyncServer) error {
|
||||
|
||||
log.Debugf("Sync request from peer %s", req.WgPubKey)
|
||||
|
||||
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||
if err != nil {
|
||||
log.Warnf("error while parsing peer's Wireguard public key %s on Sync request.", peerKey.String())
|
||||
return status.Errorf(codes.InvalidArgument, "provided wgPubKey %s is invalid", peerKey.String())
|
||||
}
|
||||
|
||||
peer, err := s.accountManager.GetPeer(peerKey.String())
|
||||
if err != nil {
|
||||
return status.Errorf(codes.PermissionDenied, "provided peer with the key wgPubKey %s is not registered", peerKey.String())
|
||||
}
|
||||
|
||||
syncReq := &proto.SyncRequest{}
|
||||
err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, syncReq)
|
||||
if err != nil {
|
||||
return status.Errorf(codes.InvalidArgument, "invalid request message")
|
||||
}
|
||||
|
||||
err = s.sendInitialSync(peerKey, peer, srv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updates := s.peersUpdateManager.CreateChannel(peerKey.String())
|
||||
err = s.accountManager.MarkPeerConnected(peerKey.String(), true)
|
||||
if err != nil {
|
||||
log.Warnf("failed marking peer as connected %s %v", peerKey, err)
|
||||
}
|
||||
|
||||
if s.config.TURNConfig.TimeBasedCredentials {
|
||||
s.turnCredentialsManager.SetupRefresh(peerKey.String())
|
||||
}
|
||||
// keep a connection to the peer and send updates when available
|
||||
for {
|
||||
select {
|
||||
// condition when there are some updates
|
||||
case update, open := <-updates:
|
||||
if !open {
|
||||
// updates channel has been closed
|
||||
return nil
|
||||
}
|
||||
log.Debugf("recevied an update for peer %s", peerKey.String())
|
||||
|
||||
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, update.Update)
|
||||
if err != nil {
|
||||
return status.Errorf(codes.Internal, "failed processing update message")
|
||||
}
|
||||
|
||||
err = srv.SendMsg(&proto.EncryptedMessage{
|
||||
WgPubKey: s.wgKey.PublicKey().String(),
|
||||
Body: encryptedResp,
|
||||
})
|
||||
if err != nil {
|
||||
return status.Errorf(codes.Internal, "failed sending update message")
|
||||
}
|
||||
log.Debugf("sent an update to peer %s", peerKey.String())
|
||||
// condition when client <-> server connection has been terminated
|
||||
case <-srv.Context().Done():
|
||||
// happens when connection drops, e.g. client disconnects
|
||||
log.Debugf("stream of peer %s has been closed", peerKey.String())
|
||||
s.peersUpdateManager.CloseChannel(peerKey.String())
|
||||
s.turnCredentialsManager.CancelRefresh(peerKey.String())
|
||||
err = s.accountManager.MarkPeerConnected(peerKey.String(), false)
|
||||
if err != nil {
|
||||
log.Warnf("failed marking peer as disconnected %s %v", peerKey, err)
|
||||
}
|
||||
// todo stop turn goroutine
|
||||
return srv.Context().Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Peer, error) {
|
||||
|
||||
meta := req.GetMeta()
|
||||
if meta == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "peer meta data was not provided")
|
||||
}
|
||||
peer, err := s.accountManager.AddPeer(req.GetSetupKey(), Peer{
|
||||
Key: peerKey.String(),
|
||||
Name: meta.GetHostname(),
|
||||
Meta: PeerSystemMeta{
|
||||
Hostname: meta.GetHostname(),
|
||||
GoOS: meta.GetGoOS(),
|
||||
Kernel: meta.GetKernel(),
|
||||
Core: meta.GetCore(),
|
||||
Platform: meta.GetPlatform(),
|
||||
OS: meta.GetOS(),
|
||||
WtVersion: meta.GetWiretrusteeVersion(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "provided setup key doesn't exists")
|
||||
}
|
||||
|
||||
peers, err := s.accountManager.GetPeersForAPeer(peer.Key)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "internal server error")
|
||||
}
|
||||
|
||||
// notify other peers of our registration
|
||||
for _, remotePeer := range peers {
|
||||
// exclude notified peer and add ourselves
|
||||
peersToSend := []*Peer{peer}
|
||||
for _, p := range peers {
|
||||
if remotePeer.Key != p.Key {
|
||||
peersToSend = append(peersToSend, p)
|
||||
}
|
||||
}
|
||||
update := toSyncResponse(s.config, peer, peersToSend, nil)
|
||||
err = s.peersUpdateManager.SendUpdate(remotePeer.Key, &UpdateMessage{Update: update})
|
||||
if err != nil {
|
||||
// todo rethink if we should keep this return
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
// Login endpoint first checks whether peer is registered under any account
|
||||
// In case it is, the login is successful
|
||||
// In case it isn't, the endpoint checks whether setup key is provided within the request and tries to register a peer.
|
||||
// In case of the successful registration login is also successful
|
||||
func (s *Server) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||
|
||||
log.Debugf("Login request from peer %s", req.WgPubKey)
|
||||
|
||||
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||
if err != nil {
|
||||
log.Warnf("error while parsing peer's Wireguard public key %s on Sync request.", req.WgPubKey)
|
||||
return nil, status.Errorf(codes.InvalidArgument, "provided wgPubKey %s is invalid", req.WgPubKey)
|
||||
}
|
||||
|
||||
peer, err := s.accountManager.GetPeer(peerKey.String())
|
||||
if err != nil {
|
||||
if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.NotFound {
|
||||
//peer doesn't exist -> check if setup key was provided
|
||||
loginReq := &proto.LoginRequest{}
|
||||
err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, loginReq)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "invalid request message")
|
||||
}
|
||||
|
||||
if loginReq.GetSetupKey() == "" {
|
||||
//absent setup key -> permission denied
|
||||
return nil, status.Errorf(codes.PermissionDenied, "provided peer with the key wgPubKey %s is not registered", peerKey.String())
|
||||
}
|
||||
|
||||
//setup key is present -> try normal registration flow
|
||||
peer, err = s.registerPeer(peerKey, loginReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
return nil, status.Error(codes.Internal, "internal server error")
|
||||
}
|
||||
}
|
||||
|
||||
// if peer has reached this point then it has logged in
|
||||
loginResp := &proto.LoginResponse{
|
||||
WiretrusteeConfig: toWiretrusteeConfig(s.config, nil),
|
||||
PeerConfig: toPeerConfig(peer),
|
||||
}
|
||||
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, loginResp)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed logging in peer")
|
||||
}
|
||||
|
||||
return &proto.EncryptedMessage{
|
||||
WgPubKey: s.wgKey.PublicKey().String(),
|
||||
Body: encryptedResp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ToResponseProto(configProto Protocol) proto.HostConfig_Protocol {
|
||||
switch configProto {
|
||||
case UDP:
|
||||
return proto.HostConfig_UDP
|
||||
case DTLS:
|
||||
return proto.HostConfig_DTLS
|
||||
case HTTP:
|
||||
return proto.HostConfig_HTTP
|
||||
case HTTPS:
|
||||
return proto.HostConfig_HTTPS
|
||||
case TCP:
|
||||
return proto.HostConfig_TCP
|
||||
default:
|
||||
//mbragin: todo something better?
|
||||
panic(fmt.Errorf("unexpected config protocol type %v", configProto))
|
||||
}
|
||||
}
|
||||
|
||||
func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *proto.WiretrusteeConfig {
|
||||
|
||||
var stuns []*proto.HostConfig
|
||||
for _, stun := range config.Stuns {
|
||||
stuns = append(stuns, &proto.HostConfig{
|
||||
Uri: stun.URI,
|
||||
Protocol: ToResponseProto(stun.Proto),
|
||||
})
|
||||
}
|
||||
var turns []*proto.ProtectedHostConfig
|
||||
for _, turn := range config.TURNConfig.Turns {
|
||||
var username string
|
||||
var password string
|
||||
if turnCredentials != nil {
|
||||
username = turnCredentials.Username
|
||||
password = turnCredentials.Password
|
||||
} else {
|
||||
username = turn.Username
|
||||
password = turn.Password
|
||||
}
|
||||
turns = append(turns, &proto.ProtectedHostConfig{
|
||||
HostConfig: &proto.HostConfig{
|
||||
Uri: turn.URI,
|
||||
Protocol: ToResponseProto(turn.Proto),
|
||||
},
|
||||
User: username,
|
||||
Password: password,
|
||||
})
|
||||
}
|
||||
|
||||
return &proto.WiretrusteeConfig{
|
||||
Stuns: stuns,
|
||||
Turns: turns,
|
||||
Signal: &proto.HostConfig{
|
||||
Uri: config.Signal.URI,
|
||||
Protocol: ToResponseProto(config.Signal.Proto),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func toPeerConfig(peer *Peer) *proto.PeerConfig {
|
||||
return &proto.PeerConfig{
|
||||
Address: peer.IP.String() + "/24", //todo make it explicit
|
||||
}
|
||||
}
|
||||
|
||||
func toRemotePeerConfig(peers []*Peer) []*proto.RemotePeerConfig {
|
||||
|
||||
remotePeers := []*proto.RemotePeerConfig{}
|
||||
for _, rPeer := range peers {
|
||||
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
|
||||
WgPubKey: rPeer.Key,
|
||||
AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)}, //todo /32
|
||||
})
|
||||
}
|
||||
|
||||
return remotePeers
|
||||
|
||||
}
|
||||
|
||||
func toSyncResponse(config *Config, peer *Peer, peers []*Peer, turnCredentials *TURNCredentials) *proto.SyncResponse {
|
||||
|
||||
wtConfig := toWiretrusteeConfig(config, turnCredentials)
|
||||
|
||||
pConfig := toPeerConfig(peer)
|
||||
|
||||
remotePeers := toRemotePeerConfig(peers)
|
||||
|
||||
return &proto.SyncResponse{
|
||||
WiretrusteeConfig: wtConfig,
|
||||
PeerConfig: pConfig,
|
||||
RemotePeers: remotePeers,
|
||||
RemotePeersIsEmpty: len(remotePeers) == 0,
|
||||
}
|
||||
}
|
||||
|
||||
// IsHealthy indicates whether the service is healthy
|
||||
func (s *Server) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Empty, error) {
|
||||
return &proto.Empty{}, nil
|
||||
}
|
||||
|
||||
// sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization
|
||||
func (s *Server) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.ManagementService_SyncServer) error {
|
||||
|
||||
peers, err := s.accountManager.GetPeersForAPeer(peer.Key)
|
||||
if err != nil {
|
||||
log.Warnf("error getting a list of peers for a peer %s", peer.Key)
|
||||
return err
|
||||
}
|
||||
|
||||
// make secret time based TURN credentials optional
|
||||
var turnCredentials *TURNCredentials
|
||||
if s.config.TURNConfig.TimeBasedCredentials {
|
||||
creds := s.turnCredentialsManager.GenerateCredentials()
|
||||
turnCredentials = &creds
|
||||
} else {
|
||||
turnCredentials = nil
|
||||
}
|
||||
plainResp := toSyncResponse(s.config, peer, peers, turnCredentials)
|
||||
|
||||
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
|
||||
if err != nil {
|
||||
return status.Errorf(codes.Internal, "error handling request")
|
||||
}
|
||||
|
||||
err = srv.Send(&proto.EncryptedMessage{
|
||||
WgPubKey: s.wgKey.PublicKey().String(),
|
||||
Body: encryptedResp,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("failed sending SyncResponse %v", err)
|
||||
return status.Errorf(codes.Internal, "error handling request")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
127
management/server/http/handler/peers.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/management/server"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
//Peers is a handler that returns peers of the account
|
||||
type Peers struct {
|
||||
accountManager *server.AccountManager
|
||||
}
|
||||
|
||||
//PeerResponse is a response sent to the client
|
||||
type PeerResponse struct {
|
||||
Name string
|
||||
IP string
|
||||
Connected bool
|
||||
LastSeen time.Time
|
||||
OS string
|
||||
}
|
||||
|
||||
//PeerRequest is a request sent by the client
|
||||
type PeerRequest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func NewPeers(accountManager *server.AccountManager) *Peers {
|
||||
return &Peers{
|
||||
accountManager: accountManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Peers) updatePeer(accountId string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
||||
req := &PeerRequest{}
|
||||
peerIp := peer.IP
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
peer, err = h.accountManager.RenamePeer(accountId, peer.Key, req.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failed updating peer %s under account %s %v", peerIp, accountId, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSONObject(w, toPeerResponse(peer))
|
||||
}
|
||||
func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
||||
_, err := h.accountManager.DeletePeer(accountId, peer.Key)
|
||||
if err != nil {
|
||||
log.Errorf("failed deleteing peer %s, %v", peer.IP, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSONObject(w, "")
|
||||
}
|
||||
|
||||
func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||
accountId := extractAccountIdFromRequestContext(r)
|
||||
vars := mux.Vars(r)
|
||||
peerId := vars["id"] //effectively peer IP address
|
||||
if len(peerId) == 0 {
|
||||
http.Error(w, "invalid peer Id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
peer, err := h.accountManager.GetPeerByIP(accountId, peerId)
|
||||
if err != nil {
|
||||
http.Error(w, "peer not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodDelete:
|
||||
h.deletePeer(accountId, peer, w, r)
|
||||
return
|
||||
case http.MethodPut:
|
||||
h.updatePeer(accountId, peer, w, r)
|
||||
return
|
||||
case http.MethodGet:
|
||||
writeJSONObject(w, toPeerResponse(peer))
|
||||
return
|
||||
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
accountId := extractAccountIdFromRequestContext(r)
|
||||
//new user -> create a new account
|
||||
account, err := h.accountManager.GetOrCreateAccount(accountId)
|
||||
if err != nil {
|
||||
log.Errorf("failed getting user account %s: %v", accountId, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
respBody := []*PeerResponse{}
|
||||
for _, peer := range account.Peers {
|
||||
respBody = append(respBody, toPeerResponse(peer))
|
||||
}
|
||||
writeJSONObject(w, respBody)
|
||||
return
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func toPeerResponse(peer *server.Peer) *PeerResponse {
|
||||
return &PeerResponse{
|
||||
Name: peer.Name,
|
||||
IP: peer.IP.String(),
|
||||
Connected: peer.Status.Connected,
|
||||
LastSeen: peer.Status.LastSeen,
|
||||
OS: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core),
|
||||
}
|
||||
}
|
||||
209
management/server/http/handler/setupkeys.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wiretrustee/wiretrustee/management/server"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SetupKeys is a handler that returns a list of setup keys of the account
|
||||
type SetupKeys struct {
|
||||
accountManager *server.AccountManager
|
||||
}
|
||||
|
||||
// SetupKeyResponse is a response sent to the client
|
||||
type SetupKeyResponse struct {
|
||||
Id string
|
||||
Key string
|
||||
Name string
|
||||
Expires time.Time
|
||||
Type server.SetupKeyType
|
||||
Valid bool
|
||||
Revoked bool
|
||||
UsedTimes int
|
||||
LastUsed time.Time
|
||||
State string
|
||||
}
|
||||
|
||||
// SetupKeyRequest is a request sent by client. This object contains fields that can be modified
|
||||
type SetupKeyRequest struct {
|
||||
Name string
|
||||
Type server.SetupKeyType
|
||||
ExpiresIn Duration
|
||||
Revoked bool
|
||||
}
|
||||
|
||||
func NewSetupKeysHandler(accountManager *server.AccountManager) *SetupKeys {
|
||||
return &SetupKeys{
|
||||
accountManager: accountManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SetupKeys) updateKey(accountId string, keyId string, w http.ResponseWriter, r *http.Request) {
|
||||
req := &SetupKeyRequest{}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var key *server.SetupKey
|
||||
if req.Revoked {
|
||||
//handle only if being revoked, don't allow to enable key again for now
|
||||
key, err = h.accountManager.RevokeSetupKey(accountId, keyId)
|
||||
if err != nil {
|
||||
http.Error(w, "failed revoking key", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(req.Name) != 0 {
|
||||
key, err = h.accountManager.RenameSetupKey(accountId, keyId, req.Name)
|
||||
if err != nil {
|
||||
http.Error(w, "failed renaming key", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if key != nil {
|
||||
writeSuccess(w, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SetupKeys) getKey(accountId string, keyId string, w http.ResponseWriter, r *http.Request) {
|
||||
account, err := h.accountManager.GetAccount(accountId)
|
||||
if err != nil {
|
||||
http.Error(w, "account doesn't exist", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
for _, key := range account.SetupKeys {
|
||||
if key.Id == keyId {
|
||||
writeSuccess(w, key)
|
||||
return
|
||||
}
|
||||
}
|
||||
http.Error(w, "setup key not found", http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.Request) {
|
||||
req := &SetupKeyRequest{}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !(req.Type == server.SetupKeyReusable || req.Type == server.SetupKeyOneOff) {
|
||||
http.Error(w, "unknown setup key type "+string(req.Type), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, req.Type, req.ExpiresIn.Duration)
|
||||
if err != nil {
|
||||
errStatus, ok := status.FromError(err)
|
||||
if ok && errStatus.Code() == codes.NotFound {
|
||||
http.Error(w, "account not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.Error(w, "failed adding setup key", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccess(w, setupKey)
|
||||
}
|
||||
|
||||
func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
||||
accountId := extractAccountIdFromRequestContext(r)
|
||||
vars := mux.Vars(r)
|
||||
keyId := vars["id"]
|
||||
if len(keyId) == 0 {
|
||||
http.Error(w, "invalid key Id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodPut:
|
||||
h.updateKey(accountId, keyId, w, r)
|
||||
return
|
||||
case http.MethodGet:
|
||||
h.getKey(accountId, keyId, w, r)
|
||||
return
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
accountId := extractAccountIdFromRequestContext(r)
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
h.createKey(accountId, w, r)
|
||||
return
|
||||
case http.MethodGet:
|
||||
|
||||
//new user -> create a new account
|
||||
account, err := h.accountManager.GetOrCreateAccount(accountId)
|
||||
if err != nil {
|
||||
log.Errorf("failed getting user account %s: %v", accountId, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
respBody := []*SetupKeyResponse{}
|
||||
for _, key := range account.SetupKeys {
|
||||
respBody = append(respBody, toResponseBody(key))
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(respBody)
|
||||
if err != nil {
|
||||
log.Errorf("failed encoding account peers %s: %v", accountId, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func writeSuccess(w http.ResponseWriter, key *server.SetupKey) {
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(w).Encode(toResponseBody(key))
|
||||
if err != nil {
|
||||
http.Error(w, "failed handling request", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func toResponseBody(key *server.SetupKey) *SetupKeyResponse {
|
||||
var state string
|
||||
if key.IsExpired() {
|
||||
state = "expired"
|
||||
} else if key.IsRevoked() {
|
||||
state = "revoked"
|
||||
} else if key.IsOverUsed() {
|
||||
state = "overused"
|
||||
} else {
|
||||
state = "valid"
|
||||
}
|
||||
return &SetupKeyResponse{
|
||||
Id: key.Id,
|
||||
Key: key.Key,
|
||||
Name: key.Name,
|
||||
Expires: key.ExpiresAt,
|
||||
Type: key.Type,
|
||||
Valid: key.IsValid(),
|
||||
Revoked: key.Revoked,
|
||||
UsedTimes: key.UsedTimes,
|
||||
LastUsed: key.LastUsed,
|
||||
State: state,
|
||||
}
|
||||
}
|
||||
59
management/server/http/handler/util.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// extractAccountIdFromRequestContext extracts accountId from the request context previously filled by the JWT token (after auth)
|
||||
func extractAccountIdFromRequestContext(r *http.Request) string {
|
||||
token := r.Context().Value("user").(*jwt.Token)
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
|
||||
//actually a user id but for now we have a 1 to 1 mapping.
|
||||
return claims["sub"].(string)
|
||||
}
|
||||
|
||||
//writeJSONObject simply writes object to the HTTP reponse in JSON format
|
||||
func writeJSONObject(w http.ResponseWriter, obj interface{}) {
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
err := json.NewEncoder(w).Encode(obj)
|
||||
if err != nil {
|
||||
http.Error(w, "failed handling request", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//Duration is used strictly for JSON requests/responses due to duration marshalling issues
|
||||
type Duration struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
func (d Duration) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(d.String())
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||
var v interface{}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
switch value := v.(type) {
|
||||
case float64:
|
||||
d.Duration = time.Duration(value)
|
||||
return nil
|
||||
case string:
|
||||
var err error
|
||||
d.Duration, err = time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.New("invalid duration")
|
||||
}
|
||||
}
|
||||
92
management/server/http/middleware/handler.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//Jwks is a collection of JSONWebKeys obtained from Config.HttpServerConfig.AuthKeysLocation
|
||||
type Jwks struct {
|
||||
Keys []JSONWebKeys `json:"keys"`
|
||||
}
|
||||
|
||||
//JSONWebKeys is a representation of a Jason Web Key
|
||||
type JSONWebKeys struct {
|
||||
Kty string `json:"kty"`
|
||||
Kid string `json:"kid"`
|
||||
Use string `json:"use"`
|
||||
N string `json:"n"`
|
||||
E string `json:"e"`
|
||||
X5c []string `json:"x5c"`
|
||||
}
|
||||
|
||||
//NewJwtMiddleware creates new middleware to verify the JWT token sent via Authorization header
|
||||
func NewJwtMiddleware(issuer string, audience string, keysLocation string) (*JWTMiddleware, error) {
|
||||
|
||||
keys, err := getPemKeys(keysLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return New(Options{
|
||||
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
|
||||
// Verify 'aud' claim
|
||||
checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(audience, false)
|
||||
if !checkAud {
|
||||
return token, errors.New("invalid audience")
|
||||
}
|
||||
// Verify 'issuer' claim
|
||||
checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(issuer, false)
|
||||
if !checkIss {
|
||||
return token, errors.New("invalid issuer")
|
||||
}
|
||||
|
||||
cert, err := getPemCert(token, keys)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert))
|
||||
return result, nil
|
||||
},
|
||||
SigningMethod: jwt.SigningMethodRS256,
|
||||
EnableAuthOnOptions: false,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func getPemKeys(keysLocation string) (*Jwks, error) {
|
||||
resp, err := http.Get(keysLocation)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var jwks = &Jwks{}
|
||||
err = json.NewDecoder(resp.Body).Decode(jwks)
|
||||
|
||||
if err != nil {
|
||||
return jwks, err
|
||||
}
|
||||
|
||||
return jwks, err
|
||||
}
|
||||
|
||||
func getPemCert(token *jwt.Token, jwks *Jwks) (string, error) {
|
||||
cert := ""
|
||||
|
||||
for k := range jwks.Keys {
|
||||
if token.Header["kid"] == jwks.Keys[k].Kid {
|
||||
cert = "-----BEGIN CERTIFICATE-----\n" + jwks.Keys[k].X5c[0] + "\n-----END CERTIFICATE-----"
|
||||
}
|
||||
}
|
||||
|
||||
if cert == "" {
|
||||
err := errors.New("unable to find appropriate key")
|
||||
return cert, err
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
237
management/server/http/middleware/jwtmiddleware.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A function called whenever an error is encountered
|
||||
type errorHandler func(w http.ResponseWriter, r *http.Request, err string)
|
||||
|
||||
// TokenExtractor is a function that takes a request as input and returns
|
||||
// either a token or an error. An error should only be returned if an attempt
|
||||
// to specify a token was found, but the information was somehow incorrectly
|
||||
// formed. In the case where a token is simply not present, this should not
|
||||
// be treated as an error. An empty string should be returned in that case.
|
||||
type TokenExtractor func(r *http.Request) (string, error)
|
||||
|
||||
// Options is a struct for specifying configuration options for the middleware.
|
||||
type Options struct {
|
||||
// The function that will return the Key to validate the JWT.
|
||||
// It can be either a shared secret or a public key.
|
||||
// Default value: nil
|
||||
ValidationKeyGetter jwt.Keyfunc
|
||||
// The name of the property in the request where the user information
|
||||
// from the JWT will be stored.
|
||||
// Default value: "user"
|
||||
UserProperty string
|
||||
// The function that will be called when there's an error validating the token
|
||||
// Default value:
|
||||
ErrorHandler errorHandler
|
||||
// A boolean indicating if the credentials are required or not
|
||||
// Default value: false
|
||||
CredentialsOptional bool
|
||||
// A function that extracts the token from the request
|
||||
// Default: FromAuthHeader (i.e., from Authorization header as bearer token)
|
||||
Extractor TokenExtractor
|
||||
// Debug flag turns on debugging output
|
||||
// Default: false
|
||||
Debug bool
|
||||
// When set, all requests with the OPTIONS method will use authentication
|
||||
// Default: false
|
||||
EnableAuthOnOptions bool
|
||||
// When set, the middelware verifies that tokens are signed with the specific signing algorithm
|
||||
// If the signing method is not constant the ValidationKeyGetter callback can be used to implement additional checks
|
||||
// Important to avoid security issues described here: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
|
||||
// Default: nil
|
||||
SigningMethod jwt.SigningMethod
|
||||
}
|
||||
|
||||
type JWTMiddleware struct {
|
||||
Options Options
|
||||
}
|
||||
|
||||
func OnError(w http.ResponseWriter, r *http.Request, err string) {
|
||||
http.Error(w, err, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
// New constructs a new Secure instance with supplied options.
|
||||
func New(options ...Options) *JWTMiddleware {
|
||||
|
||||
var opts Options
|
||||
if len(options) == 0 {
|
||||
opts = Options{}
|
||||
} else {
|
||||
opts = options[0]
|
||||
}
|
||||
|
||||
if opts.UserProperty == "" {
|
||||
opts.UserProperty = "user"
|
||||
}
|
||||
|
||||
if opts.ErrorHandler == nil {
|
||||
opts.ErrorHandler = OnError
|
||||
}
|
||||
|
||||
if opts.Extractor == nil {
|
||||
opts.Extractor = FromAuthHeader
|
||||
}
|
||||
|
||||
return &JWTMiddleware{
|
||||
Options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *JWTMiddleware) logf(format string, args ...interface{}) {
|
||||
if m.Options.Debug {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// HandlerWithNext is a special implementation for Negroni, but could be used elsewhere.
|
||||
func (m *JWTMiddleware) HandlerWithNext(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
err := m.CheckJWT(w, r)
|
||||
|
||||
// If there was an error, do not call next.
|
||||
if err == nil && next != nil {
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *JWTMiddleware) Handler(h http.Handler) http.Handler {
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Let secure process the request. If it returns an error,
|
||||
// that indicates the request should not continue.
|
||||
err := m.CheckJWT(w, r)
|
||||
|
||||
// If there was an error, do not continue.
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// FromAuthHeader is a "TokenExtractor" that takes a give request and extracts
|
||||
// the JWT token from the Authorization header.
|
||||
func FromAuthHeader(r *http.Request) (string, error) {
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
return "", nil // No error, just no token
|
||||
}
|
||||
|
||||
// TODO: Make this a bit more robust, parsing-wise
|
||||
authHeaderParts := strings.Fields(authHeader)
|
||||
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
|
||||
return "", errors.New("Authorization header format must be Bearer {token}")
|
||||
}
|
||||
|
||||
return authHeaderParts[1], nil
|
||||
}
|
||||
|
||||
// FromParameter returns a function that extracts the token from the specified
|
||||
// query string parameter
|
||||
func FromParameter(param string) TokenExtractor {
|
||||
return func(r *http.Request) (string, error) {
|
||||
return r.URL.Query().Get(param), nil
|
||||
}
|
||||
}
|
||||
|
||||
// FromFirst returns a function that runs multiple token extractors and takes the
|
||||
// first token it finds
|
||||
func FromFirst(extractors ...TokenExtractor) TokenExtractor {
|
||||
return func(r *http.Request) (string, error) {
|
||||
for _, ex := range extractors {
|
||||
token, err := ex(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if token != "" {
|
||||
return token, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *JWTMiddleware) CheckJWT(w http.ResponseWriter, r *http.Request) error {
|
||||
if !m.Options.EnableAuthOnOptions {
|
||||
if r.Method == "OPTIONS" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Use the specified token extractor to extract a token from the request
|
||||
token, err := m.Options.Extractor(r)
|
||||
|
||||
// If debugging is turned on, log the outcome
|
||||
if err != nil {
|
||||
m.logf("Error extracting JWT: %v", err)
|
||||
} else {
|
||||
m.logf("Token extracted: %s", token)
|
||||
}
|
||||
|
||||
// If an error occurs, call the error handler and return an error
|
||||
if err != nil {
|
||||
m.Options.ErrorHandler(w, r, err.Error())
|
||||
return fmt.Errorf("Error extracting token: %w", err)
|
||||
}
|
||||
|
||||
// If the token is empty...
|
||||
if token == "" {
|
||||
// Check if it was required
|
||||
if m.Options.CredentialsOptional {
|
||||
m.logf(" No credentials found (CredentialsOptional=true)")
|
||||
// No error, just no token (and that is ok given that CredentialsOptional is true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we get here, the required token is missing
|
||||
errorMsg := "Required authorization token not found"
|
||||
m.Options.ErrorHandler(w, r, errorMsg)
|
||||
m.logf(" Error: No credentials found (CredentialsOptional=false)")
|
||||
return fmt.Errorf(errorMsg)
|
||||
}
|
||||
|
||||
// Now parse the token
|
||||
parsedToken, err := jwt.Parse(token, m.Options.ValidationKeyGetter)
|
||||
|
||||
// Check if there was an error in parsing...
|
||||
if err != nil {
|
||||
m.logf("error parsing token: %v", err)
|
||||
m.Options.ErrorHandler(w, r, err.Error())
|
||||
return fmt.Errorf("Error parsing token: %w", err)
|
||||
}
|
||||
|
||||
if m.Options.SigningMethod != nil && m.Options.SigningMethod.Alg() != parsedToken.Header["alg"] {
|
||||
message := fmt.Sprintf("Expected %s signing method but token specified %s",
|
||||
m.Options.SigningMethod.Alg(),
|
||||
parsedToken.Header["alg"])
|
||||
m.logf("Error validating token algorithm: %s", message)
|
||||
m.Options.ErrorHandler(w, r, errors.New(message).Error())
|
||||
return fmt.Errorf("Error validating token algorithm: %s", message)
|
||||
}
|
||||
|
||||
// Check if the parsed token is valid...
|
||||
if !parsedToken.Valid {
|
||||
m.logf("Token is invalid")
|
||||
m.Options.ErrorHandler(w, r, "The token isn't valid")
|
||||
return errors.New("token is invalid")
|
||||
}
|
||||
|
||||
m.logf("JWT: %v", parsedToken)
|
||||
|
||||
// If we get here, everything worked and we can set the
|
||||
// user property in context.
|
||||
newRequest := r.WithContext(context.WithValue(r.Context(), m.Options.UserProperty, parsedToken)) //nolint
|
||||
// Update the current request with the new context information.
|
||||
*r = *newRequest
|
||||
return nil
|
||||
}
|
||||