Compare commits
52 Commits
v0.1.0-bet
...
v0.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dca2d6953 | ||
|
|
6b7d4cf644 | ||
|
|
edd4125742 | ||
|
|
7bf9793f85 | ||
|
|
fcbf980588 | ||
|
|
d08e5efbce | ||
|
|
95ef8547f3 | ||
|
|
ed1e4dfc51 | ||
|
|
4d34fb4e64 | ||
|
|
1fb8b74cd2 | ||
|
|
d040cfed7e | ||
|
|
2c729fe5cc | ||
|
|
e9066b4651 | ||
|
|
673e807528 | ||
|
|
892080bc38 | ||
|
|
2d39f6ccae | ||
|
|
0b2c26847b | ||
|
|
595ea0d4f8 | ||
|
|
f714868fdd | ||
|
|
81821a1f39 | ||
|
|
842b143a48 | ||
|
|
1323a74db0 | ||
|
|
74485d3b13 | ||
|
|
bef3b3392b | ||
|
|
fcea3c99d4 | ||
|
|
96799a25b5 | ||
|
|
07291cdb93 | ||
|
|
21139938c1 | ||
|
|
5cf2d0a6a9 | ||
|
|
8551afe04e | ||
|
|
1685817171 | ||
|
|
e17f662683 | ||
|
|
a764fb870c | ||
|
|
cabff941ac | ||
|
|
b5f35dfb5e | ||
|
|
1d426b7f81 | ||
|
|
e4f9406d44 | ||
|
|
7c79ff62ee | ||
|
|
32c369257b | ||
|
|
08dd719aa1 | ||
|
|
84c714dd93 | ||
|
|
996c8d7c62 | ||
|
|
25e68ce493 | ||
|
|
4881dcbd51 | ||
|
|
d505f70972 | ||
|
|
6a80684378 | ||
|
|
2624a7c4e6 | ||
|
|
9a412e7bf1 | ||
|
|
b5d1690129 | ||
|
|
d4bec15ca3 | ||
|
|
3212aca7c7 | ||
|
|
b97a2251d3 |
33
.github/workflows/release.yml
vendored
@@ -51,28 +51,15 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
|
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
|
||||||
|
UPLOAD_DEBIAN_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
|
||||||
|
UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
|
||||||
|
|
||||||
-
|
-
|
||||||
id: get_version
|
name: Trigger Windows binaries sign pipeline
|
||||||
uses: battila7/get-version-action@v2
|
uses: benc-uk/workflow-dispatch@v1
|
||||||
-
|
|
||||||
name: Install makensis
|
|
||||||
run: sudo apt update && sudo apt install -y nsis nsis-pluginapi
|
|
||||||
-
|
|
||||||
name: Download EnvVar Plugin
|
|
||||||
run: curl -L -o EnVar_plugin.zip https://nsis.sourceforge.io/mediawiki/images/7/7f/EnVar_plugin.zip
|
|
||||||
-
|
|
||||||
name: Extract EnVar plugin
|
|
||||||
run: sudo 7z x -o"/usr/share/nsis/" EnVar_plugin.zip
|
|
||||||
-
|
|
||||||
name: Generate Windows installer
|
|
||||||
run: makensis -V4 client/installer.nsis
|
|
||||||
env:
|
|
||||||
APPVER: ${{ steps.get_version.outputs.major }}.${{ steps.get_version.outputs.minor }}.${{ steps.get_version.outputs.patch }}.${{ github.run_id }}
|
|
||||||
-
|
|
||||||
name: Upload windows installer to release page
|
|
||||||
uses: svenstaro/upload-release-action@v2
|
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
workflow: Sign windows bin and installer
|
||||||
file: wiretrustee-installer.exe
|
repo: wiretrustee/windows-sign-pipeline
|
||||||
asset_name: wiretrustee_installer_${{ steps.get_version.outputs.version-without-v }}_windows_amd64.exe
|
ref: v0.0.1
|
||||||
tag: ${{ github.ref }}
|
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
|
||||||
|
inputs: '{ "tag": "${{ github.ref }}" }'
|
||||||
2
.gitignore
vendored
@@ -4,3 +4,5 @@ dist/
|
|||||||
.env
|
.env
|
||||||
conf.json
|
conf.json
|
||||||
http-cmds.sh
|
http-cmds.sh
|
||||||
|
infrastructure_files/management.json
|
||||||
|
infrastructure_files/docker-compose.yml
|
||||||
171
.goreleaser.yaml
@@ -13,13 +13,18 @@ builds:
|
|||||||
- arm
|
- arm
|
||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
|
- mips
|
||||||
|
gomips:
|
||||||
|
- hardfloat
|
||||||
|
- softfloat
|
||||||
ignore:
|
ignore:
|
||||||
- goos: darwin
|
|
||||||
goarch: arm64
|
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm
|
goarch: arm
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
tags:
|
tags:
|
||||||
- load_wintun_from_rsrc
|
- load_wintun_from_rsrc
|
||||||
|
|
||||||
@@ -32,6 +37,10 @@ builds:
|
|||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
|
- arm
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
|
|
||||||
- id: wiretrustee-signal
|
- id: wiretrustee-signal
|
||||||
dir: signal
|
dir: signal
|
||||||
@@ -42,29 +51,86 @@ builds:
|
|||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
|
- arm
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
archives:
|
archives:
|
||||||
- builds:
|
- builds:
|
||||||
- wiretrustee
|
- wiretrustee
|
||||||
nfpms:
|
nfpms:
|
||||||
- maintainer: Wiretrustee <wiretrustee@wiretrustee.com>
|
- maintainer: Wiretrustee <dev@wiretrustee.com>
|
||||||
description: Wiretrustee project.
|
description: Wiretrustee client.
|
||||||
homepage: https://wiretrustee.com/
|
homepage: https://wiretrustee.com/
|
||||||
|
id: deb
|
||||||
builds:
|
builds:
|
||||||
- wiretrustee
|
- wiretrustee
|
||||||
formats:
|
formats:
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
|
||||||
contents:
|
|
||||||
- src: release_files/wiretrustee.service
|
|
||||||
dst: /lib/systemd/system/wiretrustee.service
|
|
||||||
|
|
||||||
- src: release_files/wiretrustee.json
|
|
||||||
dst: /etc/wiretrustee/wiretrustee.json
|
|
||||||
type: "config|noreplace"
|
|
||||||
|
|
||||||
scripts:
|
scripts:
|
||||||
postinstall: "release_files/post_install.sh"
|
postinstall: "release_files/post_install.sh"
|
||||||
|
preremove: "release_files/pre_remove.sh"
|
||||||
|
|
||||||
|
- maintainer: Wiretrustee <dev@wiretrustee.com>
|
||||||
|
description: Wiretrustee client.
|
||||||
|
homepage: https://wiretrustee.com/
|
||||||
|
id: rpm
|
||||||
|
builds:
|
||||||
|
- wiretrustee
|
||||||
|
formats:
|
||||||
|
- rpm
|
||||||
|
|
||||||
|
scripts:
|
||||||
|
postinstall: "release_files/post_install.sh"
|
||||||
|
preremove: "release_files/pre_remove.sh"
|
||||||
dockers:
|
dockers:
|
||||||
|
- image_templates:
|
||||||
|
- wiretrustee/wiretrustee:{{ .Version }}-amd64
|
||||||
|
ids:
|
||||||
|
- wiretrustee
|
||||||
|
goarch: amd64
|
||||||
|
use: buildx
|
||||||
|
dockerfile: client/Dockerfile
|
||||||
|
build_flag_templates:
|
||||||
|
- "--platform=linux/amd64"
|
||||||
|
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||||
|
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||||
|
- image_templates:
|
||||||
|
- wiretrustee/wiretrustee:{{ .Version }}-arm64v8
|
||||||
|
ids:
|
||||||
|
- wiretrustee
|
||||||
|
goarch: arm64
|
||||||
|
use: buildx
|
||||||
|
dockerfile: client/Dockerfile
|
||||||
|
build_flag_templates:
|
||||||
|
- "--platform=linux/arm64"
|
||||||
|
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||||
|
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||||
|
- image_templates:
|
||||||
|
- wiretrustee/wiretrustee:{{ .Version }}-arm
|
||||||
|
ids:
|
||||||
|
- wiretrustee
|
||||||
|
goarch: arm
|
||||||
|
goarm: 6
|
||||||
|
use: buildx
|
||||||
|
dockerfile: client/Dockerfile
|
||||||
|
build_flag_templates:
|
||||||
|
- "--platform=linux/arm"
|
||||||
|
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||||
|
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||||
- image_templates:
|
- image_templates:
|
||||||
- wiretrustee/signal:{{ .Version }}-amd64
|
- wiretrustee/signal:{{ .Version }}-amd64
|
||||||
ids:
|
ids:
|
||||||
@@ -95,6 +161,22 @@ dockers:
|
|||||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||||
|
- image_templates:
|
||||||
|
- wiretrustee/signal:{{ .Version }}-arm
|
||||||
|
ids:
|
||||||
|
- wiretrustee-signal
|
||||||
|
goarch: arm
|
||||||
|
goarm: 6
|
||||||
|
use: buildx
|
||||||
|
dockerfile: signal/Dockerfile
|
||||||
|
build_flag_templates:
|
||||||
|
- "--platform=linux/arm"
|
||||||
|
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||||
|
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||||
- image_templates:
|
- image_templates:
|
||||||
- wiretrustee/management:{{ .Version }}-amd64
|
- wiretrustee/management:{{ .Version }}-amd64
|
||||||
ids:
|
ids:
|
||||||
@@ -125,6 +207,22 @@ dockers:
|
|||||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||||
|
- image_templates:
|
||||||
|
- wiretrustee/management:{{ .Version }}-arm
|
||||||
|
ids:
|
||||||
|
- wiretrustee-mgmt
|
||||||
|
goarch: arm
|
||||||
|
goarm: 6
|
||||||
|
use: buildx
|
||||||
|
dockerfile: management/Dockerfile
|
||||||
|
build_flag_templates:
|
||||||
|
- "--platform=linux/arm"
|
||||||
|
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||||
|
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||||
- image_templates:
|
- image_templates:
|
||||||
- wiretrustee/management:{{ .Version }}-debug-amd64
|
- wiretrustee/management:{{ .Version }}-debug-amd64
|
||||||
ids:
|
ids:
|
||||||
@@ -156,30 +254,63 @@ dockers:
|
|||||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||||
|
|
||||||
|
- image_templates:
|
||||||
|
- wiretrustee/management:{{ .Version }}-debug-arm
|
||||||
|
ids:
|
||||||
|
- wiretrustee-mgmt
|
||||||
|
goarch: arm
|
||||||
|
goarm: 6
|
||||||
|
use: buildx
|
||||||
|
dockerfile: management/Dockerfile.debug
|
||||||
|
build_flag_templates:
|
||||||
|
- "--platform=linux/arm"
|
||||||
|
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||||
|
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||||
|
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||||
|
- "--label=maintainer=wiretrustee@wiretrustee.com"
|
||||||
docker_manifests:
|
docker_manifests:
|
||||||
|
- name_template: wiretrustee/wiretrustee:{{ .Version }}
|
||||||
|
image_templates:
|
||||||
|
- wiretrustee/wiretrustee:{{ .Version }}-arm64v8
|
||||||
|
- wiretrustee/wiretrustee:{{ .Version }}-arm
|
||||||
|
- wiretrustee/wiretrustee:{{ .Version }}-amd64
|
||||||
|
|
||||||
|
- name_template: wiretrustee/wiretrustee:latest
|
||||||
|
image_templates:
|
||||||
|
- wiretrustee/wiretrustee:{{ .Version }}-arm64v8
|
||||||
|
- wiretrustee/wiretrustee:{{ .Version }}-arm
|
||||||
|
- wiretrustee/wiretrustee:{{ .Version }}-amd64
|
||||||
|
|
||||||
- name_template: wiretrustee/signal:{{ .Version }}
|
- name_template: wiretrustee/signal:{{ .Version }}
|
||||||
image_templates:
|
image_templates:
|
||||||
- wiretrustee/signal:{{ .Version }}-arm64v8
|
- wiretrustee/signal:{{ .Version }}-arm64v8
|
||||||
|
- wiretrustee/signal:{{ .Version }}-arm
|
||||||
- wiretrustee/signal:{{ .Version }}-amd64
|
- wiretrustee/signal:{{ .Version }}-amd64
|
||||||
|
|
||||||
- name_template: wiretrustee/signal:latest
|
- name_template: wiretrustee/signal:latest
|
||||||
image_templates:
|
image_templates:
|
||||||
- wiretrustee/signal:{{ .Version }}-arm64v8
|
- wiretrustee/signal:{{ .Version }}-arm64v8
|
||||||
|
- wiretrustee/signal:{{ .Version }}-arm
|
||||||
- wiretrustee/signal:{{ .Version }}-amd64
|
- wiretrustee/signal:{{ .Version }}-amd64
|
||||||
|
|
||||||
- name_template: wiretrustee/management:{{ .Version }}
|
- name_template: wiretrustee/management:{{ .Version }}
|
||||||
image_templates:
|
image_templates:
|
||||||
- wiretrustee/management:{{ .Version }}-arm64v8
|
- wiretrustee/management:{{ .Version }}-arm64v8
|
||||||
|
- wiretrustee/management:{{ .Version }}-arm
|
||||||
- wiretrustee/management:{{ .Version }}-amd64
|
- wiretrustee/management:{{ .Version }}-amd64
|
||||||
|
|
||||||
- name_template: wiretrustee/management:latest
|
- name_template: wiretrustee/management:latest
|
||||||
image_templates:
|
image_templates:
|
||||||
- wiretrustee/management:{{ .Version }}-arm64v8
|
- wiretrustee/management:{{ .Version }}-arm64v8
|
||||||
|
- wiretrustee/management:{{ .Version }}-arm
|
||||||
- wiretrustee/management:{{ .Version }}-amd64
|
- wiretrustee/management:{{ .Version }}-amd64
|
||||||
|
|
||||||
- name_template: wiretrustee/management:debug-latest
|
- name_template: wiretrustee/management:debug-latest
|
||||||
image_templates:
|
image_templates:
|
||||||
- wiretrustee/management:{{ .Version }}-debug-arm64v8
|
- wiretrustee/management:{{ .Version }}-debug-arm64v8
|
||||||
|
- wiretrustee/management:{{ .Version }}-debug-arm
|
||||||
- wiretrustee/management:{{ .Version }}-debug-amd64
|
- wiretrustee/management:{{ .Version }}-debug-amd64
|
||||||
|
|
||||||
brews:
|
brews:
|
||||||
@@ -197,3 +328,19 @@ brews:
|
|||||||
license: "BSD3"
|
license: "BSD3"
|
||||||
test: |
|
test: |
|
||||||
system "#{bin}/{{ .ProjectName }} -h"
|
system "#{bin}/{{ .ProjectName }} -h"
|
||||||
|
|
||||||
|
uploads:
|
||||||
|
- name: debian
|
||||||
|
ids:
|
||||||
|
- deb
|
||||||
|
mode: archive
|
||||||
|
target: https://pkgs.wiretrustee.com/debian/pool/{{ .ArtifactName }};deb.distribution=stable;deb.component=main;deb.architecture={{ if .Arm }}armhf{{ else }}{{ .Arch }}{{ end }};deb.package=
|
||||||
|
username: dev@wiretrustee.com
|
||||||
|
method: PUT
|
||||||
|
- name: yum
|
||||||
|
ids:
|
||||||
|
- rpm
|
||||||
|
mode: archive
|
||||||
|
target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }}
|
||||||
|
username: dev@wiretrustee.com
|
||||||
|
method: PUT
|
||||||
|
|||||||
229
README.md
@@ -1,32 +1,59 @@
|
|||||||
# Wiretrustee
|
<div align="center">
|
||||||
|
|
||||||
A WireGuard®-based mesh network that connects your devices into a single private network.
|
<p align="center">
|
||||||
|
<img width="250" src="docs/media/logo-full.png"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<img src="https://img.shields.io/badge/license-BSD--3-blue" />
|
||||||
|
<img src="https://img.shields.io/docker/pulls/wiretrustee/management" />
|
||||||
|
<img src="https://badgen.net/badge/Open%20Source%3F/Yes%21/blue?icon=github" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>
|
||||||
|
Start using Wiretrustee at <a href="https://app.wiretrustee.com/">app.wiretrustee.com</a>
|
||||||
|
<br/>
|
||||||
|
See <a href="https://docs.wiretrustee.com">Documentation</a>
|
||||||
|
<br/>
|
||||||
|
Join our <a href="https://join.slack.com/t/wiretrustee/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A">Slack channel</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
**Wiretrustee is an open-source VPN platform built on top of WireGuard® making it easy to create secure private networks for your organization or home.**
|
||||||
|
|
||||||
|
It requires zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, vpn gateways, and so forth.
|
||||||
|
|
||||||
|
**Wiretrustee automates Wireguard-based networks, offering a management layer with:**
|
||||||
|
* Centralized Peer IP management with a UI dashboard.
|
||||||
|
* Encrypted peer-to-peet connections without a centralized VPN gateway.
|
||||||
|
* Automatic Peer discovery and configuration.
|
||||||
|
* UDP hole punching to establish peer-to-peer connections behind NAT, firewall, and without a public static IP.
|
||||||
|
* Connection relay fallback in case a peer-to-peer connection is not possible.
|
||||||
|
* Multitenancy (coming soon).
|
||||||
|
* Client application SSO with MFA (coming soon).
|
||||||
|
* Access Controls (coming soon).
|
||||||
|
* Activity Monitoring (coming soon).
|
||||||
|
* Private DNS (coming baoon)
|
||||||
|
|
||||||
|
### Secure peer-to-peer VPN in minutes
|
||||||
|
<p float="left" align="middle">
|
||||||
|
<img src="docs/media/peerA.gif" width="400"/>
|
||||||
|
<img src="docs/media/peerB.gif" width="400"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
**Note**: The `main` branch may be in an *unstable or even broken state* during development. For stable versions, see [releases](https://github.com/wiretrustee/wiretrustee/releases).
|
**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)
|
Hosted demo version:
|
||||||
|
[https://app.wiretrustee.com/](https://app.wiretrustee.com/peers).
|
||||||
Please don't use the hosted demonstration version for production purposes.
|
|
||||||
|
|
||||||
[UI Dashboard Repo](https://github.com/wiretrustee/wiretrustee-dashboard)
|
[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
|
### A bit on Wiretrustee internals
|
||||||
* Wiretrustee features a Management Service that offers peer IP management and network updates distribution (e.g. when new peer joins the network).
|
* Wiretrustee features a Management Service that offers peer IP management and network updates distribution (e.g. when new peer joins the network).
|
||||||
@@ -44,116 +71,124 @@ Please don't use the hosted demonstration version for production purposes.
|
|||||||
|
|
||||||
### Client Installation
|
### Client Installation
|
||||||
#### Linux
|
#### Linux
|
||||||
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases)
|
|
||||||
2. Download the latest release (**Switch VERSION to the latest**):
|
|
||||||
|
|
||||||
**Debian packages**
|
**APT/Debian**
|
||||||
```shell
|
1. Add the repository:
|
||||||
wget https://github.com/wiretrustee/wiretrustee/releases/download/v<VERSION>/wiretrustee_<VERSION>_linux_amd64.deb
|
```shell
|
||||||
```
|
sudo apt-get update
|
||||||
3. Install the package
|
sudo apt-get install ca-certificates curl gnupg -y
|
||||||
```shell
|
curl -L https://pkgs.wiretrustee.com/debian/public.key | sudo apt-key add -
|
||||||
sudo dpkg -i wiretrustee_<VERSION>_linux_amd64.deb
|
echo 'deb https://pkgs.wiretrustee.com/debian stable main' | sudo tee /etc/apt/sources.list.d/wiretrustee.list
|
||||||
```
|
```
|
||||||
**Fedora/Centos packages**
|
2. Install the package
|
||||||
```shell
|
```shell
|
||||||
wget https://github.com/wiretrustee/wiretrustee/releases/download/v<VERSION>/wiretrustee_<VERSION>_linux_amd64.rpm
|
sudo apt-get update
|
||||||
```
|
sudo apt-get install wiretrustee
|
||||||
3. Install the package
|
```
|
||||||
```shell
|
**RPM/Red hat**
|
||||||
sudo rpm -i wiretrustee_<VERSION>_linux_amd64.rpm
|
1. Add the repository:
|
||||||
```
|
```shell
|
||||||
|
cat <<EOF | sudo tee /etc/yum.repos.d/wiretrustee.repo
|
||||||
|
[Wiretrustee]
|
||||||
|
name=Wiretrustee
|
||||||
|
baseurl=https://pkgs.wiretrustee.com/yum/
|
||||||
|
enabled=1
|
||||||
|
gpgcheck=0
|
||||||
|
gpgkey=https://pkgs.wiretrustee.com/yum/repodata/repomd.xml.key
|
||||||
|
repo_gpgcheck=1
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
2. Install the package
|
||||||
|
```shell
|
||||||
|
sudo yum install wiretrustee
|
||||||
|
```
|
||||||
#### MACOS
|
#### MACOS
|
||||||
**Brew install**
|
**Brew install**
|
||||||
1. Download and install Brew at https://brew.sh/
|
1. Download and install Brew at https://brew.sh/
|
||||||
2. Install the client
|
2. Install the client
|
||||||
```shell
|
```shell
|
||||||
brew install wiretrustee/client/wiretrustee
|
brew install wiretrustee/client/wiretrustee
|
||||||
```
|
```
|
||||||
**Installation from binary**
|
**Installation from binary**
|
||||||
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases/latest)
|
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases/latest)
|
||||||
2. Download the latest release (**Switch VERSION to the latest**):
|
2. Download the latest release (**Switch VERSION to the latest**):
|
||||||
```shell
|
```shell
|
||||||
curl -o ./wiretrustee_<VERSION>_darwin_amd64.tar.gz https://github.com/wiretrustee/wiretrustee/releases/download/v<VERSION>/wiretrustee_<VERSION>_darwin_amd64.tar.gz
|
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
|
3. Decompress
|
||||||
```shell
|
```shell
|
||||||
tar xcf ./wiretrustee_<VERSION>_darwin_amd64.tar.gz
|
tar xcf ./wiretrustee_<VERSION>_darwin_amd64.tar.gz
|
||||||
sudo mv wiretrusee /usr/local/bin/wiretrustee
|
sudo mv wiretrusee /usr/local/bin/wiretrustee
|
||||||
chmod +x /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:
|
After that you may need to add /usr/local/bin in your MAC's PATH environment variable:
|
||||||
````shell
|
````shell
|
||||||
export PATH=$PATH:/usr/local/bin
|
export PATH=$PATH:/usr/local/bin
|
||||||
````
|
````
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases/latest)
|
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**):
|
2. Download the latest Windows release installer ```wiretrustee_installer_<VERSION>_windows_amd64.exe``` (**Switch VERSION to the latest**):
|
||||||
3. Proceed with installation steps
|
3. Proceed with installation steps
|
||||||
4. This will install the client in the C:\\Program Files\\Wiretrustee and add the client service
|
4. This will install the client in the C:\\Program Files\\Wiretrustee and add the client service
|
||||||
5. After installing you can follow the [Client Configuration](#Client-Configuration) steps.
|
5. After installing, you can follow the [Client Configuration](#Client-Configuration) steps.
|
||||||
> To uninstall the client and service, you can use Add/Remove programs
|
> To uninstall the client and service, you can use Add/Remove programs
|
||||||
|
|
||||||
### Client Configuration
|
### Client Configuration
|
||||||
1. Login to the Management Service. You need to have a `setup key` in hand (see ).
|
1. Login to the Management Service. You need to have a `setup key` in hand (see ).
|
||||||
|
|
||||||
For **Unix** systems:
|
For **Unix** systems:
|
||||||
|
```shell
|
||||||
|
sudo wiretrustee up --setup-key <SETUP KEY>
|
||||||
|
```
|
||||||
|
For **Windows** systems, start powershell as administrator and:
|
||||||
|
```shell
|
||||||
|
wiretrustee up --setup-key <SETUP KEY>
|
||||||
|
```
|
||||||
|
For **Docker**, you can run with the following command:
|
||||||
```shell
|
```shell
|
||||||
sudo wiretrustee login --setup-key <SETUP KEY>
|
docker run --network host --privileged --rm -d -e WT_SETUP_KEY=<SETUP KEY> -v wiretrustee-client:/etc/wiretrustee wiretrustee/wiretrustee:<TAG>
|
||||||
```
|
```
|
||||||
For **Windows** systems:
|
> TAG > 0.3.0 version
|
||||||
```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:
|
Alternatively, if you are hosting your own Management Service provide `--management-url` property pointing to your Management Service:
|
||||||
```shell
|
```shell
|
||||||
sudo wiretrustee login --setup-key <SETUP KEY> --management-url https://localhost:33073
|
sudo wiretrustee up --setup-key <SETUP KEY> --management-url https://localhost:33073
|
||||||
```
|
```
|
||||||
|
|
||||||
You could also omit `--setup-key` property. In this case the tool will prompt it the key.
|
> You could also omit `--setup-key` property. In this case the tool will prompt it the key.
|
||||||
|
|
||||||
2. Start Wiretrustee:
|
|
||||||
|
|
||||||
For **MACOS** you will just start the service:
|
2. Check your IP:
|
||||||
````shell
|
For **MACOS** you will just start the service:
|
||||||
sudo wiretrustee up
|
````shell
|
||||||
# or
|
sudo ipconfig getifaddr utun100
|
||||||
sudo wiretrustee up & # to run it in background
|
````
|
||||||
````
|
|
||||||
For **Linux** systems:
|
For **Linux** systems:
|
||||||
```shell
|
```shell
|
||||||
sudo systemctl restart wiretrustee.service
|
ip addr show wt0
|
||||||
sudo systemctl status wiretrustee.service
|
```
|
||||||
```
|
|
||||||
For **Windows** systems:
|
For **Windows** systems:
|
||||||
```shell
|
```shell
|
||||||
.\wiretrustee.exe service start
|
netsh interface ip show config name="wt0"
|
||||||
```
|
```
|
||||||
> You may need to run Powershell as Administrator
|
|
||||||
|
|
||||||
3. Check your IP:
|
3. Repeat on other machines.
|
||||||
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
|
||||||
|
|
||||||
### Running Management, Signal and Coturn
|
|
||||||
Wiretrustee uses [Auth0](https://auth0.com) for user authentication and authorization, therefore you will need to create a free account
|
Wiretrustee uses [Auth0](https://auth0.com) for user authentication and authorization, therefore you will need to create a free account
|
||||||
and configure AUTH0 variables in the compose file (dashboard and management).
|
and configure Auth0 variables in the compose file (dashboard) and in the management config file.
|
||||||
|
We chose Auth0 to "outsource" the user management part of our platform because we believe that implementing a proper user auth is not a trivial task and requires 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.
|
||||||
|
|
||||||
Under infrastructure_files we have a docker-compose example to run both, Wiretrustee Management and Signal services, plus an instance of [Coturn](https://github.com/coturn/coturn), it also provides a turnserver.conf file as a simple example of Coturn configuration.
|
Configuring Wiretrustee Auth0 integration:
|
||||||
|
- check [How to run](https://github.com/wiretrustee/wiretrustee-dashboard#how-to-run) to obtain Auth0 environment variables for UI Dashboard
|
||||||
|
- set these variables in the [environment section of the docker-compose file](https://github.com/wiretrustee/wiretrustee/blob/main/infrastructure_files/docker-compose.yml)
|
||||||
|
- check [Auth0 Golang API Guide](https://auth0.com/docs/quickstart/backend/golang) to obtain ```AuthIssuer```, ```AuthAudience```, and ```AuthKeysLocation```
|
||||||
|
- set these properties in the [management config files](https://github.com/wiretrustee/wiretrustee/blob/main/infrastructure_files/management.json#L33)
|
||||||
|
|
||||||
|
|
||||||
|
Under infrastructure_files we have a docker-compose example to run Dashboard, Wiretrustee Management and Signal services, plus an instance of [Coturn](https://github.com/coturn/coturn), it also provides a turnserver.conf file as a simple example of Coturn configuration.
|
||||||
You can edit the turnserver.conf file and change its Realm setting (defaults to wiretrustee.com) to your own domain and user setting (defaults to username1:password1) to **proper credentials**.
|
You can edit the turnserver.conf file and change its Realm setting (defaults to wiretrustee.com) to your own domain and user setting (defaults to username1:password1) to **proper credentials**.
|
||||||
|
|
||||||
The example is set to use the official images from Wiretrustee and Coturn, you can find our documentation to run the signal server in docker in [Running the Signal service](#running-the-signal-service), the management in [Management](./management/README.md), and the Coturn official documentation [here](https://hub.docker.com/r/coturn/coturn).
|
The example is set to use the official images from Wiretrustee and Coturn, you can find our documentation to run the signal server in docker in [Running the Signal service](#running-the-signal-service), the management in [Management](./management/README.md), and the Coturn official documentation [here](https://hub.docker.com/r/coturn/coturn).
|
||||||
|
|||||||
4
client/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
FROM gcr.io/distroless/base:debug
|
||||||
|
ENV WT_LOG_FILE=console
|
||||||
|
ENTRYPOINT [ "/go/bin/wiretrustee","up"]
|
||||||
|
COPY wiretrustee /go/bin/wiretrustee
|
||||||
@@ -18,22 +18,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
setupKey string
|
|
||||||
|
|
||||||
loginCmd = &cobra.Command{
|
loginCmd = &cobra.Command{
|
||||||
Use: "login",
|
Use: "login",
|
||||||
Short: "login to the Wiretrustee Management Service (first run)",
|
Short: "login to the Wiretrustee Management Service (first run)",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
|
||||||
err := util.InitLog(logLevel, logFile)
|
err := util.InitLog(logLevel, logFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed initializing log %v", err)
|
log.Errorf("failed initializing log %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := internal.GetConfig(managementURL, configPath)
|
config, err := internal.GetConfig(managementURL, configPath, preSharedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed getting config %s %v", configPath, err)
|
log.Errorf("failed getting config %s %v", configPath, err)
|
||||||
//os.Exit(ExitSetupFailed)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +40,6 @@ var (
|
|||||||
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
||||||
//os.Exit(ExitSetupFailed)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +54,6 @@ var (
|
|||||||
mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
mgmClient, err := mgm.NewClient(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
|
log.Errorf("failed connecting to Management Service %s %v", config.ManagementURL.String(), err)
|
||||||
//os.Exit(ExitSetupFailed)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Debugf("connected to anagement Service %s", config.ManagementURL.String())
|
log.Debugf("connected to anagement Service %s", config.ManagementURL.String())
|
||||||
@@ -64,21 +61,18 @@ var (
|
|||||||
serverKey, err := mgmClient.GetServerPublicKey()
|
serverKey, err := mgmClient.GetServerPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed while getting Management Service public key: %v", err)
|
log.Errorf("failed while getting Management Service public key: %v", err)
|
||||||
//os.Exit(ExitSetupFailed)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = loginPeer(*serverKey, mgmClient, setupKey)
|
_, err = loginPeer(*serverKey, mgmClient, setupKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed logging-in peer on Management Service : %v", err)
|
log.Errorf("failed logging-in peer on Management Service : %v", err)
|
||||||
//os.Exit(ExitSetupFailed)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mgmClient.Close()
|
err = mgmClient.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed closing Management Service client: %v", err)
|
log.Errorf("failed closing Management Service client: %v", err)
|
||||||
//os.Exit(ExitSetupFailed)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +145,3 @@ func promptPeerSetupKey() (string, error) {
|
|||||||
|
|
||||||
return "", s.Err()
|
return "", s.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
loginCmd.PersistentFlags().StringVar(&setupKey, "setup-key", "", "Setup key obtained from the Management Service Dashboard (used to register peer)")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,17 +2,15 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
"strings"
|
||||||
|
"syscall"
|
||||||
const (
|
|
||||||
// ExitSetupFailed defines exit code
|
|
||||||
ExitSetupFailed = 1
|
|
||||||
DefaultConfigPath = ""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -22,15 +20,17 @@ var (
|
|||||||
defaultLogFile string
|
defaultLogFile string
|
||||||
logFile string
|
logFile string
|
||||||
managementURL string
|
managementURL string
|
||||||
|
setupKey string
|
||||||
rootCmd = &cobra.Command{
|
preSharedKey string
|
||||||
|
rootCmd = &cobra.Command{
|
||||||
Use: "wiretrustee",
|
Use: "wiretrustee",
|
||||||
Short: "",
|
Short: "",
|
||||||
Long: "",
|
Long: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execution control channel for stopCh signal
|
// Execution control channel for stopCh signal
|
||||||
stopCh chan int
|
stopCh chan int
|
||||||
|
cleanupCh chan struct{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute executes the root command.
|
// Execute executes the root command.
|
||||||
@@ -40,6 +40,7 @@ func Execute() error {
|
|||||||
func init() {
|
func init() {
|
||||||
|
|
||||||
stopCh = make(chan int)
|
stopCh = make(chan int)
|
||||||
|
cleanupCh = make(chan struct{})
|
||||||
|
|
||||||
defaultConfigPath = "/etc/wiretrustee/config.json"
|
defaultConfigPath = "/etc/wiretrustee/config.json"
|
||||||
defaultLogFile = "/var/log/wiretrustee/client.log"
|
defaultLogFile = "/var/log/wiretrustee/client.log"
|
||||||
@@ -52,9 +53,12 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().StringVar(&configPath, "config", defaultConfigPath, "Wiretrustee config file location")
|
rootCmd.PersistentFlags().StringVar(&configPath, "config", defaultConfigPath, "Wiretrustee config file location")
|
||||||
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "sets Wiretrustee log level")
|
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "sets Wiretrustee log level")
|
||||||
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Wiretrustee log path. If console is specified the the log will be output to stdout")
|
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Wiretrustee log path. If console is specified the the log will be output to stdout")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&setupKey, "setup-key", "", "Setup key obtained from the Management Service Dashboard (used to register peer)")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&preSharedKey, "preshared-key", "", "Sets Wireguard PreSharedKey property. If set, then only peers that have the same key can communicate.")
|
||||||
rootCmd.AddCommand(serviceCmd)
|
rootCmd.AddCommand(serviceCmd)
|
||||||
rootCmd.AddCommand(upCmd)
|
rootCmd.AddCommand(upCmd)
|
||||||
rootCmd.AddCommand(loginCmd)
|
rootCmd.AddCommand(loginCmd)
|
||||||
|
rootCmd.AddCommand(versionCmd)
|
||||||
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service
|
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service
|
||||||
serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service
|
serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service
|
||||||
}
|
}
|
||||||
@@ -62,11 +66,36 @@ func init() {
|
|||||||
// SetupCloseHandler handles SIGTERM signal and exits with success
|
// SetupCloseHandler handles SIGTERM signal and exits with success
|
||||||
func SetupCloseHandler() {
|
func SetupCloseHandler() {
|
||||||
c := make(chan os.Signal, 1)
|
c := make(chan os.Signal, 1)
|
||||||
signal.Notify(c, os.Interrupt)
|
signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
for range c {
|
for range c {
|
||||||
fmt.Println("\r- Ctrl+C pressed in Terminal")
|
log.Info("shutdown signal received")
|
||||||
stopCh <- 0
|
stopCh <- 0
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetFlagsFromEnvVars reads and updates flag values from environment variables with prefix WT_
|
||||||
|
func SetFlagsFromEnvVars() {
|
||||||
|
flags := rootCmd.PersistentFlags()
|
||||||
|
flags.VisitAll(func(f *pflag.Flag) {
|
||||||
|
|
||||||
|
envVar := FlagNameToEnvVar(f.Name)
|
||||||
|
|
||||||
|
if value, present := os.LookupEnv(envVar); present {
|
||||||
|
err := flags.Set(f.Name, value)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("unable to configure flag %s using variable %s, err: %v", f.Name, envVar, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlagNameToEnvVar converts flag name to environment var name adding a prefix,
|
||||||
|
// replacing dashes and making all uppercase (e.g. setup-keys is converted to WT_SETUP_KEYS)
|
||||||
|
func FlagNameToEnvVar(f string) string {
|
||||||
|
prefix := "WT_"
|
||||||
|
parsed := strings.ReplaceAll(f, "-", "_")
|
||||||
|
upper := strings.ToUpper(parsed)
|
||||||
|
return prefix + upper
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,6 +34,3 @@ var (
|
|||||||
Short: "manages wiretrustee service",
|
Short: "manages wiretrustee service",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,23 +4,35 @@ import (
|
|||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/wiretrustee/wiretrustee/util"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *program) Start(s service.Service) error {
|
func (p *program) Start(service.Service) error {
|
||||||
|
|
||||||
// Start should not block. Do the actual work async.
|
// Start should not block. Do the actual work async.
|
||||||
log.Info("starting service") //nolint
|
log.Info("starting service") //nolint
|
||||||
go func() {
|
go func() {
|
||||||
err := upCmd.RunE(p.cmd, p.args)
|
err := runClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Errorf("stopped Wiretrustee client app due to error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *program) Stop(s service.Service) error {
|
func (p *program) Stop(service.Service) error {
|
||||||
stopCh <- 1
|
go func() {
|
||||||
|
stopCh <- 1
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-cleanupCh:
|
||||||
|
case <-time.After(time.Second * 10):
|
||||||
|
log.Warnf("failed waiting for service cleanup, terminating")
|
||||||
|
}
|
||||||
|
log.Info("stopped Wiretrustee service") //nolint
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +41,15 @@ var (
|
|||||||
Use: "run",
|
Use: "run",
|
||||||
Short: "runs wiretrustee as service",
|
Short: "runs wiretrustee as service",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
|
||||||
|
err := util.InitLog(logLevel, logFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed initializing log %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SetupCloseHandler()
|
||||||
|
|
||||||
prg := &program{
|
prg := &program{
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
@@ -54,19 +75,26 @@ var (
|
|||||||
startCmd = &cobra.Command{
|
startCmd = &cobra.Command{
|
||||||
Use: "start",
|
Use: "start",
|
||||||
Short: "starts wiretrustee service",
|
Short: "starts wiretrustee service",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
|
||||||
|
err := util.InitLog(logLevel, logFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed initializing log %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
s, err := newSVC(&program{}, newSVCConfig())
|
s, err := newSVC(&program{}, newSVCConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.PrintErrln(err)
|
cmd.PrintErrln(err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
err = s.Start()
|
err = s.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.PrintErrln(err)
|
cmd.PrintErrln(err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
cmd.Printf("Wiretrustee service has been started")
|
cmd.Println("Wiretrustee service has been started")
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -76,7 +104,12 @@ var (
|
|||||||
Use: "stop",
|
Use: "stop",
|
||||||
Short: "stops wiretrustee service",
|
Short: "stops wiretrustee service",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
|
||||||
|
err := util.InitLog(logLevel, logFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed initializing log %v", err)
|
||||||
|
}
|
||||||
s, err := newSVC(&program{}, newSVCConfig())
|
s, err := newSVC(&program{}, newSVCConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.PrintErrln(err)
|
cmd.PrintErrln(err)
|
||||||
@@ -87,7 +120,7 @@ var (
|
|||||||
cmd.PrintErrln(err)
|
cmd.PrintErrln(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cmd.Printf("Wiretrustee service has been stopped")
|
cmd.Println("Wiretrustee service has been stopped")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -97,7 +130,12 @@ var (
|
|||||||
Use: "restart",
|
Use: "restart",
|
||||||
Short: "restarts wiretrustee service",
|
Short: "restarts wiretrustee service",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
|
||||||
|
err := util.InitLog(logLevel, logFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed initializing log %v", err)
|
||||||
|
}
|
||||||
s, err := newSVC(&program{}, newSVCConfig())
|
s, err := newSVC(&program{}, newSVCConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.PrintErrln(err)
|
cmd.PrintErrln(err)
|
||||||
@@ -108,10 +146,7 @@ var (
|
|||||||
cmd.PrintErrln(err)
|
cmd.PrintErrln(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cmd.Printf("Wiretrustee service has been restarted")
|
cmd.Println("Wiretrustee service has been restarted")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ var (
|
|||||||
installCmd = &cobra.Command{
|
installCmd = &cobra.Command{
|
||||||
Use: "install",
|
Use: "install",
|
||||||
Short: "installs wiretrustee service",
|
Short: "installs wiretrustee service",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
|
||||||
svcConfig := newSVCConfig()
|
svcConfig := newSVCConfig()
|
||||||
|
|
||||||
@@ -30,15 +31,16 @@ var (
|
|||||||
s, err := newSVC(&program{}, svcConfig)
|
s, err := newSVC(&program{}, svcConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.PrintErrln(err)
|
cmd.PrintErrln(err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.Install()
|
err = s.Install()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.PrintErrln(err)
|
cmd.PrintErrln(err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
cmd.Printf("Wiretrustee service has been installed")
|
cmd.Println("Wiretrustee service has been installed")
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -48,6 +50,7 @@ var (
|
|||||||
Use: "uninstall",
|
Use: "uninstall",
|
||||||
Short: "uninstalls wiretrustee service from system",
|
Short: "uninstalls wiretrustee service from system",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
SetFlagsFromEnvVars()
|
||||||
|
|
||||||
s, err := newSVC(&program{}, newSVCConfig())
|
s, err := newSVC(&program{}, newSVCConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -60,10 +63,7 @@ var (
|
|||||||
cmd.PrintErrln(err)
|
cmd.PrintErrln(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cmd.Printf("Wiretrustee has been uninstalled")
|
cmd.Println("Wiretrustee has been uninstalled")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
}
|
|
||||||
|
|||||||
230
client/cmd/up.go
@@ -2,123 +2,83 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
"github.com/kardianos/service"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/wiretrustee/wiretrustee/client/internal"
|
"github.com/wiretrustee/wiretrustee/client/internal"
|
||||||
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
||||||
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
|
mgmProto "github.com/wiretrustee/wiretrustee/management/proto"
|
||||||
signal "github.com/wiretrustee/wiretrustee/signal/client"
|
signal "github.com/wiretrustee/wiretrustee/signal/client"
|
||||||
"github.com/wiretrustee/wiretrustee/util"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
upCmd = &cobra.Command{
|
upCmd = &cobra.Command{
|
||||||
Use: "up",
|
Use: "up",
|
||||||
Short: "start wiretrustee",
|
Short: "install, login and start wiretrustee client",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := util.InitLog(logLevel, logFile)
|
SetFlagsFromEnvVars()
|
||||||
|
err := loginCmd.RunE(cmd, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed initializing log %v", err)
|
return err
|
||||||
|
}
|
||||||
|
if logFile == "console" {
|
||||||
|
return runClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := newSVC(&program{}, newSVCConfig())
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := internal.ReadConfig(managementURL, configPath)
|
srvStatus, err := s.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed reading config %s %v", configPath, err)
|
if err == service.ErrNotInstalled {
|
||||||
return err
|
log.Infof("%s. Installing it now", err.Error())
|
||||||
|
e := installCmd.RunE(cmd, args)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warnf("failed retrieving service status: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if srvStatus == service.StatusRunning {
|
||||||
//validate our peer's Wireguard PRIVATE key
|
stopCmd.Run(cmd, args)
|
||||||
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())
|
return startCmd.RunE(cmd, args)
|
||||||
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
|
// createEngineConfig converts configuration received from Management Service to EngineConfig
|
||||||
func createEngineConfig(key wgtypes.Key, config *internal.Config, wtConfig *mgmProto.WiretrusteeConfig, peerConfig *mgmProto.PeerConfig) (*internal.EngineConfig, error) {
|
func createEngineConfig(key wgtypes.Key, config *internal.Config, peerConfig *mgmProto.PeerConfig) (*internal.EngineConfig, error) {
|
||||||
iFaceBlackList := make(map[string]struct{})
|
iFaceBlackList := make(map[string]struct{})
|
||||||
for i := 0; i < len(config.IFaceBlackList); i += 2 {
|
for i := 0; i < len(config.IFaceBlackList); i += 2 {
|
||||||
iFaceBlackList[config.IFaceBlackList[i]] = struct{}{}
|
iFaceBlackList[config.IFaceBlackList[i]] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &internal.EngineConfig{
|
engineConf := &internal.EngineConfig{
|
||||||
WgIface: config.WgIface,
|
WgIface: config.WgIface,
|
||||||
WgAddr: peerConfig.Address,
|
WgAddr: peerConfig.Address,
|
||||||
IFaceBlackList: iFaceBlackList,
|
IFaceBlackList: iFaceBlackList,
|
||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
if config.PreSharedKey != "" {
|
||||||
|
preSharedKey, err := wgtypes.ParseKey(config.PreSharedKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
engineConf.PreSharedKey = &preSharedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return engineConf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// connectToSignal creates Signal Service client and established a connection
|
// connectToSignal creates Signal Service client and established a connection
|
||||||
@@ -163,7 +123,113 @@ func connectToManagement(ctx context.Context, managementAddr string, ourPrivateK
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("peer logged in to Management Service %s", managementAddr)
|
log.Debugf("peer logged in to Management Service %s", managementAddr)
|
||||||
|
|
||||||
return client, loginResp, nil
|
return client, loginResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runClient() error {
|
||||||
|
var backOff = &backoff.ExponentialBackOff{
|
||||||
|
InitialInterval: time.Second,
|
||||||
|
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||||
|
Multiplier: backoff.DefaultMultiplier,
|
||||||
|
MaxInterval: 10 * time.Second,
|
||||||
|
MaxElapsedTime: 24 * 3 * time.Hour, //stop the client after 3 days trying (must be a huge problem, e.g permission denied)
|
||||||
|
Stop: backoff.Stop,
|
||||||
|
Clock: backoff.SystemClock,
|
||||||
|
}
|
||||||
|
|
||||||
|
operation := func() error {
|
||||||
|
|
||||||
|
config, err := internal.ReadConfig(managementURL, configPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed reading config %s %v", configPath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//validate our peer's Wireguard PRIVATE key
|
||||||
|
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
mgmTlsEnabled := false
|
||||||
|
if config.ManagementURL.Scheme == "https" {
|
||||||
|
mgmTlsEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect (just a connection, no stream yet) and login to Management Service to get an initial global Wiretrustee config
|
||||||
|
mgmClient, loginResp, err := connectToManagement(ctx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
|
||||||
|
signalClient, err := connectToSignal(ctx, loginResp.GetWiretrusteeConfig(), myPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerConfig := loginResp.GetPeerConfig()
|
||||||
|
|
||||||
|
engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, ctx)
|
||||||
|
err = engine.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error while starting Wiretrustee Connection Engine: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print("Wiretrustee engine started, my IP is: ", peerConfig.Address)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-stopCh:
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
|
||||||
|
backOff.Reset()
|
||||||
|
|
||||||
|
err = mgmClient.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed closing Management Service client %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = signalClient.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed closing Signal Service client %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = engine.Stop()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed stopping engine %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
cleanupCh <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Info("stopped Wiretrustee client")
|
||||||
|
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := backoff.Retry(operation, backOff)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("exiting client retry loop due to unrecoverable error: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/wiretrustee/wiretrustee/iface"
|
"github.com/wiretrustee/wiretrustee/iface"
|
||||||
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
mgmt "github.com/wiretrustee/wiretrustee/management/server"
|
||||||
"github.com/wiretrustee/wiretrustee/util"
|
"github.com/wiretrustee/wiretrustee/util"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -37,24 +34,6 @@ func TestUp_Start(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUp_ShouldFail_On_NoConfig(t *testing.T) {
|
|
||||||
|
|
||||||
tempDir := t.TempDir()
|
|
||||||
confPath := tempDir + "/config.json"
|
|
||||||
mgmtURL := fmt.Sprintf("http://%s", mgmAddr)
|
|
||||||
rootCmd.SetArgs([]string{
|
|
||||||
"up",
|
|
||||||
"--config",
|
|
||||||
confPath,
|
|
||||||
"--management-url",
|
|
||||||
mgmtURL,
|
|
||||||
})
|
|
||||||
err := rootCmd.Execute()
|
|
||||||
if err == nil || !errors.Is(err, os.ErrNotExist) {
|
|
||||||
t.Errorf("expecting login command to fail on absence of config")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUp(t *testing.T) {
|
func TestUp(t *testing.T) {
|
||||||
|
|
||||||
defer iface.Close()
|
defer iface.Close()
|
||||||
@@ -65,24 +44,17 @@ func TestUp(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rootCmd.SetArgs([]string{
|
rootCmd.SetArgs([]string{
|
||||||
"login",
|
"up",
|
||||||
"--config",
|
"--config",
|
||||||
confPath,
|
confPath,
|
||||||
"--setup-key",
|
"--setup-key",
|
||||||
"A2C8E62B-38F5-4553-B31E-DD66C696CEBB",
|
"A2C8E62B-38F5-4553-B31E-DD66C696CEBB",
|
||||||
"--management-url",
|
"--management-url",
|
||||||
mgmtURL.String(),
|
mgmtURL.String(),
|
||||||
})
|
"--log-file",
|
||||||
err = rootCmd.Execute()
|
"console",
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rootCmd.SetArgs([]string{
|
|
||||||
"up",
|
|
||||||
"--config",
|
|
||||||
confPath,
|
|
||||||
})
|
})
|
||||||
go func() {
|
go func() {
|
||||||
err = rootCmd.Execute()
|
err = rootCmd.Execute()
|
||||||
|
|||||||
14
client/cmd/version.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
|
var (
|
||||||
|
Version string
|
||||||
|
versionCmd = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "prints wiretrustee version",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
cmd.Println(Version)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -106,6 +106,7 @@ SectionEnd
|
|||||||
Section Uninstall
|
Section Uninstall
|
||||||
${INSTALL_TYPE}
|
${INSTALL_TYPE}
|
||||||
|
|
||||||
|
Exec '"$INSTDIR\${MAIN_APP_EXE}" service stop'
|
||||||
Exec '"$INSTDIR\${MAIN_APP_EXE}" service uninstall'
|
Exec '"$INSTDIR\${MAIN_APP_EXE}" service uninstall'
|
||||||
# wait the service uninstall take unblock the executable
|
# wait the service uninstall take unblock the executable
|
||||||
Sleep 3000
|
Sleep 3000
|
||||||
|
|||||||
@@ -28,13 +28,14 @@ func init() {
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
// Wireguard private key of local peer
|
// Wireguard private key of local peer
|
||||||
PrivateKey string
|
PrivateKey string
|
||||||
|
PreSharedKey string
|
||||||
ManagementURL *url.URL
|
ManagementURL *url.URL
|
||||||
WgIface string
|
WgIface string
|
||||||
IFaceBlackList []string
|
IFaceBlackList []string
|
||||||
}
|
}
|
||||||
|
|
||||||
//createNewConfig creates a new config generating a new Wireguard key and saving to file
|
//createNewConfig creates a new config generating a new Wireguard key and saving to file
|
||||||
func createNewConfig(managementURL string, configPath string) (*Config, error) {
|
func createNewConfig(managementURL string, configPath string, preSharedKey string) (*Config, error) {
|
||||||
wgKey := generateKey()
|
wgKey := generateKey()
|
||||||
config := &Config{PrivateKey: wgKey, WgIface: iface.WgInterfaceDefault, IFaceBlackList: []string{}}
|
config := &Config{PrivateKey: wgKey, WgIface: iface.WgInterfaceDefault, IFaceBlackList: []string{}}
|
||||||
if managementURL != "" {
|
if managementURL != "" {
|
||||||
@@ -47,6 +48,10 @@ func createNewConfig(managementURL string, configPath string) (*Config, error) {
|
|||||||
config.ManagementURL = managementURLDefault
|
config.ManagementURL = managementURLDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if preSharedKey != "" {
|
||||||
|
config.PreSharedKey = preSharedKey
|
||||||
|
}
|
||||||
|
|
||||||
config.IFaceBlackList = []string{iface.WgInterfaceDefault, "tun0"}
|
config.IFaceBlackList = []string{iface.WgInterfaceDefault, "tun0"}
|
||||||
|
|
||||||
err := util.WriteJson(configPath, config)
|
err := util.WriteJson(configPath, config)
|
||||||
@@ -93,11 +98,11 @@ func ReadConfig(managementURL string, configPath string) (*Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetConfig reads existing config or generates a new one
|
// GetConfig reads existing config or generates a new one
|
||||||
func GetConfig(managementURL string, configPath string) (*Config, error) {
|
func GetConfig(managementURL string, configPath string, preSharedKey string) (*Config, error) {
|
||||||
|
|
||||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||||
log.Infof("generating new config %s", configPath)
|
log.Infof("generating new config %s", configPath)
|
||||||
return createNewConfig(managementURL, configPath)
|
return createNewConfig(managementURL, configPath, preSharedKey)
|
||||||
} else {
|
} else {
|
||||||
return ReadConfig(managementURL, configPath)
|
return ReadConfig(managementURL, configPath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ type ConnConfig struct {
|
|||||||
// Remote Wireguard public key
|
// Remote Wireguard public key
|
||||||
RemoteWgKey wgtypes.Key
|
RemoteWgKey wgtypes.Key
|
||||||
|
|
||||||
|
PreSharedKey *wgtypes.Key
|
||||||
|
|
||||||
StunTurnURLS []*ice.URL
|
StunTurnURLS []*ice.URL
|
||||||
|
|
||||||
iFaceBlackList map[string]struct{}
|
iFaceBlackList map[string]struct{}
|
||||||
@@ -115,7 +117,7 @@ func NewConnection(config ConnConfig,
|
|||||||
closeCond: NewCond(),
|
closeCond: NewCond(),
|
||||||
connected: NewCond(),
|
connected: NewCond(),
|
||||||
agent: nil,
|
agent: nil,
|
||||||
wgProxy: NewWgProxy(config.WgIface, config.RemoteWgKey.String(), config.WgAllowedIPs, config.WgListenAddr),
|
wgProxy: NewWgProxy(config.WgIface, config.RemoteWgKey.String(), config.WgAllowedIPs, config.WgListenAddr, config.PreSharedKey),
|
||||||
Status: StatusDisconnected,
|
Status: StatusDisconnected,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,12 +140,18 @@ func (conn *Connection) Open(timeout time.Duration) error {
|
|||||||
return !ok
|
return !ok
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
conn.agent = a
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conn.agent = a
|
||||||
|
defer func() {
|
||||||
|
err := conn.agent.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
err = conn.listenOnLocalCandidates()
|
err = conn.listenOnLocalCandidates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -160,13 +168,13 @@ func (conn *Connection) Open(timeout time.Duration) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn.Status = StatusConnecting
|
conn.Status = StatusConnecting
|
||||||
log.Infof("trying to connect to peer %s", conn.Config.RemoteWgKey.String())
|
log.Debugf("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)
|
// wait until credentials have been sent from the remote peer (will arrive via a signal server)
|
||||||
select {
|
select {
|
||||||
case remoteAuth := <-conn.remoteAuthChannel:
|
case remoteAuth := <-conn.remoteAuthChannel:
|
||||||
|
|
||||||
log.Infof("got a connection confirmation from peer %s", conn.Config.RemoteWgKey.String())
|
log.Debugf("got a connection confirmation from peer %s", conn.Config.RemoteWgKey.String())
|
||||||
|
|
||||||
err = conn.agent.GatherCandidates()
|
err = conn.agent.GatherCandidates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -186,8 +194,11 @@ func (conn *Connection) Open(timeout time.Duration) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useProxy := useProxy(pair)
|
||||||
|
|
||||||
// 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.
|
// 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) {
|
if !useProxy {
|
||||||
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)
|
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))
|
err = conn.wgProxy.StartLocal(fmt.Sprintf("%s:%d", pair.Remote.Address(), iface.WgPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -195,19 +206,17 @@ func (conn *Connection) Open(timeout time.Duration) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.Infof("establishing secure tunnel to peer %s via selected candidate pair %s", conn.Config.RemoteWgKey.String(), pair)
|
log.Debugf("establishing secure tunnel to peer %s via selected candidate pair %s", conn.Config.RemoteWgKey.String(), pair)
|
||||||
err = conn.wgProxy.Start(remoteConn)
|
err = conn.wgProxy.Start(remoteConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pair.Remote.Type() == ice.CandidateTypeRelay || pair.Local.Type() == ice.CandidateTypeRelay {
|
relayed := pair.Remote.Type() == ice.CandidateTypeRelay || pair.Local.Type() == ice.CandidateTypeRelay
|
||||||
log.Infof("using relay with peer %s", conn.Config.RemoteWgKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.Status = StatusConnected
|
conn.Status = StatusConnected
|
||||||
log.Infof("opened connection to peer %s", conn.Config.RemoteWgKey.String())
|
log.Infof("opened connection to peer %s [localProxy=%v, relayed=%v]", conn.Config.RemoteWgKey.String(), useProxy, relayed)
|
||||||
case <-conn.closeCond.C:
|
case <-conn.closeCond.C:
|
||||||
conn.Status = StatusDisconnected
|
conn.Status = StatusDisconnected
|
||||||
return fmt.Errorf("connection to peer %s has been closed", conn.Config.RemoteWgKey.String())
|
return fmt.Errorf("connection to peer %s has been closed", conn.Config.RemoteWgKey.String())
|
||||||
@@ -271,7 +280,7 @@ func (conn *Connection) Close() error {
|
|||||||
var err error
|
var err error
|
||||||
conn.closeCond.Do(func() {
|
conn.closeCond.Do(func() {
|
||||||
|
|
||||||
log.Warnf("closing connection to peer %s", conn.Config.RemoteWgKey.String())
|
log.Debugf("closing connection to peer %s", conn.Config.RemoteWgKey.String())
|
||||||
|
|
||||||
if a := conn.agent; a != nil {
|
if a := conn.agent; a != nil {
|
||||||
e := a.Close()
|
e := a.Close()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
ice "github.com/pion/ice/v2"
|
"github.com/pion/ice/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/wiretrustee/wiretrustee/iface"
|
"github.com/wiretrustee/wiretrustee/iface"
|
||||||
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
mgm "github.com/wiretrustee/wiretrustee/management/client"
|
||||||
@@ -30,6 +30,8 @@ type EngineConfig struct {
|
|||||||
WgPrivateKey wgtypes.Key
|
WgPrivateKey wgtypes.Key
|
||||||
// IFaceBlackList is a list of network interfaces to ignore when discovering connection candidates (ICE related)
|
// IFaceBlackList is a list of network interfaces to ignore when discovering connection candidates (ICE related)
|
||||||
IFaceBlackList map[string]struct{}
|
IFaceBlackList map[string]struct{}
|
||||||
|
|
||||||
|
PreSharedKey *wgtypes.Key
|
||||||
}
|
}
|
||||||
|
|
||||||
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
|
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
|
||||||
@@ -57,6 +59,8 @@ type Engine struct {
|
|||||||
TURNs []*ice.URL
|
TURNs []*ice.URL
|
||||||
|
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer is an instance of the Connection Peer
|
// Peer is an instance of the Connection Peer
|
||||||
@@ -66,7 +70,7 @@ type Peer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewEngine creates a new Connection Engine
|
// NewEngine creates a new Connection Engine
|
||||||
func NewEngine(signalClient *signal.Client, mgmClient *mgm.Client, config *EngineConfig, cancel context.CancelFunc) *Engine {
|
func NewEngine(signalClient *signal.Client, mgmClient *mgm.Client, config *EngineConfig, cancel context.CancelFunc, ctx context.Context) *Engine {
|
||||||
return &Engine{
|
return &Engine{
|
||||||
signal: signalClient,
|
signal: signalClient,
|
||||||
mgmClient: mgmClient,
|
mgmClient: mgmClient,
|
||||||
@@ -77,17 +81,25 @@ func NewEngine(signalClient *signal.Client, mgmClient *mgm.Client, config *Engin
|
|||||||
STUNs: []*ice.URL{},
|
STUNs: []*ice.URL{},
|
||||||
TURNs: []*ice.URL{},
|
TURNs: []*ice.URL{},
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) Stop() error {
|
func (e *Engine) Stop() error {
|
||||||
|
err := e.removeAllPeerConnections()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
log.Debugf("removing Wiretrustee interface %s", e.config.WgIface)
|
log.Debugf("removing Wiretrustee interface %s", e.config.WgIface)
|
||||||
err := iface.Close()
|
err = iface.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed closing Wiretrustee interface %s %v", e.config.WgIface, err)
|
log.Errorf("failed closing Wiretrustee interface %s %v", e.config.WgIface, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("stopped Wiretrustee Engine")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,27 +139,32 @@ func (e *Engine) Start() error {
|
|||||||
|
|
||||||
// initializePeer peer agent attempt to open connection
|
// initializePeer peer agent attempt to open connection
|
||||||
func (e *Engine) initializePeer(peer Peer) {
|
func (e *Engine) initializePeer(peer Peer) {
|
||||||
var backOff = &backoff.ExponentialBackOff{
|
var backOff = backoff.WithContext(&backoff.ExponentialBackOff{
|
||||||
InitialInterval: backoff.DefaultInitialInterval,
|
InitialInterval: backoff.DefaultInitialInterval,
|
||||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||||
Multiplier: backoff.DefaultMultiplier,
|
Multiplier: backoff.DefaultMultiplier,
|
||||||
MaxInterval: 5 * time.Second,
|
MaxInterval: 5 * time.Second,
|
||||||
MaxElapsedTime: time.Duration(0), //never stop
|
MaxElapsedTime: 0, //never stop
|
||||||
Stop: backoff.Stop,
|
Stop: backoff.Stop,
|
||||||
Clock: backoff.SystemClock,
|
Clock: backoff.SystemClock,
|
||||||
}
|
}, e.ctx)
|
||||||
|
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
|
|
||||||
|
if e.signal.GetStatus() != signal.StreamConnected {
|
||||||
|
return fmt.Errorf("not opening connection to peer because Signal is unavailable")
|
||||||
|
}
|
||||||
|
|
||||||
_, err := e.openPeerConnection(e.wgPort, e.config.WgPrivateKey, peer)
|
_, err := e.openPeerConnection(e.wgPort, e.config.WgPrivateKey, peer)
|
||||||
e.peerMux.Lock()
|
e.peerMux.Lock()
|
||||||
defer e.peerMux.Unlock()
|
defer e.peerMux.Unlock()
|
||||||
if _, ok := e.conns[peer.WgPubKey]; !ok {
|
if _, ok := e.conns[peer.WgPubKey]; !ok {
|
||||||
log.Infof("removing connection attempt with Peer: %v, not retrying", peer.WgPubKey)
|
log.Debugf("removed connection attempt to peer: %v, not retrying", peer.WgPubKey)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnln(err)
|
log.Debugf("retrying connection because of error: %s", err.Error())
|
||||||
log.Warnln("retrying connection because of error: ", err.Error())
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -172,6 +189,19 @@ func (e *Engine) removePeerConnections(peers []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Engine) removeAllPeerConnections() error {
|
||||||
|
log.Debugf("removing all peer connections")
|
||||||
|
e.peerMux.Lock()
|
||||||
|
defer e.peerMux.Unlock()
|
||||||
|
for peer := range e.conns {
|
||||||
|
err := e.removePeerConnection(peer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// removePeerConnection closes existing peer connection and removes peer
|
// removePeerConnection closes existing peer connection and removes peer
|
||||||
func (e *Engine) removePeerConnection(peerKey string) error {
|
func (e *Engine) removePeerConnection(peerKey string) error {
|
||||||
conn, exists := e.conns[peerKey]
|
conn, exists := e.conns[peerKey]
|
||||||
@@ -179,6 +209,7 @@ func (e *Engine) removePeerConnection(peerKey string) error {
|
|||||||
delete(e.conns, peerKey)
|
delete(e.conns, peerKey)
|
||||||
return conn.Close()
|
return conn.Close()
|
||||||
}
|
}
|
||||||
|
log.Infof("removed connection to peer %s", peerKey)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +240,7 @@ func (e *Engine) openPeerConnection(wgPort int, myKey wgtypes.Key, peer Peer) (*
|
|||||||
RemoteWgKey: remoteKey,
|
RemoteWgKey: remoteKey,
|
||||||
StunTurnURLS: append(e.STUNs, e.TURNs...),
|
StunTurnURLS: append(e.STUNs, e.TURNs...),
|
||||||
iFaceBlackList: e.config.IFaceBlackList,
|
iFaceBlackList: e.config.IFaceBlackList,
|
||||||
|
PreSharedKey: e.config.PreSharedKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
signalOffer := func(uFrag string, pwd string) error {
|
signalOffer := func(uFrag string, pwd string) error {
|
||||||
@@ -307,10 +339,12 @@ func (e *Engine) receiveManagementEvents() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// happens if management is unavailable for a long time.
|
||||||
|
// We want to cancel the operation of the whole client
|
||||||
e.cancel()
|
e.cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Infof("connected to Management Service updates stream")
|
log.Debugf("stopped receiving updates from Management Service")
|
||||||
}()
|
}()
|
||||||
log.Debugf("connecting to Management Service updates stream")
|
log.Debugf("connecting to Management Service updates stream")
|
||||||
}
|
}
|
||||||
@@ -389,68 +423,77 @@ func (e *Engine) updatePeers(remotePeers []*mgmProto.RemotePeerConfig) error {
|
|||||||
|
|
||||||
// receiveSignalEvents connects to the Signal Service event stream to negotiate connection with remote peers
|
// receiveSignalEvents connects to the Signal Service event stream to negotiate connection with remote peers
|
||||||
func (e *Engine) receiveSignalEvents() {
|
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()
|
go func() {
|
||||||
defer e.syncMsgMux.Unlock()
|
// connect to a stream of messages coming from the signal server
|
||||||
|
err := e.signal.Receive(func(msg *sProto.Message) error {
|
||||||
|
|
||||||
conn := e.conns[msg.Key]
|
e.syncMsgMux.Lock()
|
||||||
if conn == nil {
|
defer e.syncMsgMux.Unlock()
|
||||||
return fmt.Errorf("wrongly addressed message %s", msg.Key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if conn.Config.RemoteWgKey.String() != msg.Key {
|
conn := e.conns[msg.Key]
|
||||||
return fmt.Errorf("unknown peer %s", msg.Key)
|
if conn == nil {
|
||||||
}
|
return fmt.Errorf("wrongly addressed message %s", msg.Key)
|
||||||
|
|
||||||
switch msg.GetBody().Type {
|
|
||||||
case sProto.Body_OFFER:
|
|
||||||
remoteCred, err := signal.UnMarshalCredential(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
err = conn.OnOffer(IceCredentials{
|
|
||||||
uFrag: remoteCred.UFrag,
|
|
||||||
pwd: remoteCred.Pwd,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if conn.Config.RemoteWgKey.String() != msg.Key {
|
||||||
return err
|
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
|
return nil
|
||||||
case sProto.Body_ANSWER:
|
})
|
||||||
remoteCred, err := signal.UnMarshalCredential(msg)
|
if err != nil {
|
||||||
if err != nil {
|
// happens if signal is unavailable for a long time.
|
||||||
return err
|
// We want to cancel the operation of the whole client
|
||||||
}
|
e.cancel()
|
||||||
err = conn.OnAnswer(IceCredentials{
|
return
|
||||||
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.WaitStreamConnected()
|
||||||
})
|
|
||||||
|
|
||||||
e.signal.WaitConnected()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,27 +4,30 @@ import (
|
|||||||
ice "github.com/pion/ice/v2"
|
ice "github.com/pion/ice/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/wiretrustee/wiretrustee/iface"
|
"github.com/wiretrustee/wiretrustee/iface"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WgProxy an instance of an instance of the Connection Wireguard Proxy
|
// WgProxy an instance of an instance of the Connection Wireguard Proxy
|
||||||
type WgProxy struct {
|
type WgProxy struct {
|
||||||
iface string
|
iface string
|
||||||
remoteKey string
|
remoteKey string
|
||||||
allowedIps string
|
allowedIps string
|
||||||
wgAddr string
|
wgAddr string
|
||||||
close chan struct{}
|
close chan struct{}
|
||||||
wgConn net.Conn
|
wgConn net.Conn
|
||||||
|
preSharedKey *wgtypes.Key
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWgProxy creates a new Connection Wireguard Proxy
|
// NewWgProxy creates a new Connection Wireguard Proxy
|
||||||
func NewWgProxy(iface string, remoteKey string, allowedIps string, wgAddr string) *WgProxy {
|
func NewWgProxy(iface string, remoteKey string, allowedIps string, wgAddr string, preSharedKey *wgtypes.Key) *WgProxy {
|
||||||
return &WgProxy{
|
return &WgProxy{
|
||||||
iface: iface,
|
iface: iface,
|
||||||
remoteKey: remoteKey,
|
remoteKey: remoteKey,
|
||||||
allowedIps: allowedIps,
|
allowedIps: allowedIps,
|
||||||
wgAddr: wgAddr,
|
wgAddr: wgAddr,
|
||||||
close: make(chan struct{}),
|
close: make(chan struct{}),
|
||||||
|
preSharedKey: preSharedKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +51,7 @@ func (p *WgProxy) Close() error {
|
|||||||
|
|
||||||
// StartLocal configure the interface with a peer using a direct IP:Port endpoint to the remote host
|
// StartLocal configure the interface with a peer using a direct IP:Port endpoint to the remote host
|
||||||
func (p *WgProxy) StartLocal(host string) error {
|
func (p *WgProxy) StartLocal(host string) error {
|
||||||
err := iface.UpdatePeer(p.iface, p.remoteKey, p.allowedIps, DefaultWgKeepAlive, host)
|
err := iface.UpdatePeer(p.iface, p.remoteKey, p.allowedIps, DefaultWgKeepAlive, host, p.preSharedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error while configuring Wireguard peer [%s] %s", p.remoteKey, err.Error())
|
log.Errorf("error while configuring Wireguard peer [%s] %s", p.remoteKey, err.Error())
|
||||||
return err
|
return err
|
||||||
@@ -67,7 +70,7 @@ func (p *WgProxy) Start(remoteConn *ice.Conn) error {
|
|||||||
p.wgConn = wgConn
|
p.wgConn = wgConn
|
||||||
// add local proxy connection as a Wireguard peer
|
// add local proxy connection as a Wireguard peer
|
||||||
err = iface.UpdatePeer(p.iface, p.remoteKey, p.allowedIps, DefaultWgKeepAlive,
|
err = iface.UpdatePeer(p.iface, p.remoteKey, p.allowedIps, DefaultWgKeepAlive,
|
||||||
wgConn.LocalAddr().String())
|
wgConn.LocalAddr().String(), p.preSharedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error while configuring Wireguard peer [%s] %s", p.remoteKey, err.Error())
|
log.Errorf("error while configuring Wireguard peer [%s] %s", p.remoteKey, err.Error())
|
||||||
return err
|
return err
|
||||||
@@ -87,18 +90,16 @@ func (p *WgProxy) proxyToRemotePeer(remoteConn *ice.Conn) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-p.close:
|
case <-p.close:
|
||||||
log.Infof("stopped proxying from remote peer %s due to closed connection", p.remoteKey)
|
log.Debugf("stopped proxying from remote peer %s due to closed connection", p.remoteKey)
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
n, err := p.wgConn.Read(buf)
|
n, err := p.wgConn.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//log.Warnln("failed reading from peer: ", err.Error())
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = remoteConn.Write(buf[:n])
|
_, err = remoteConn.Write(buf[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//log.Warnln("failed writing to remote peer: ", err.Error())
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,18 +114,16 @@ func (p *WgProxy) proxyToLocalWireguard(remoteConn *ice.Conn) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-p.close:
|
case <-p.close:
|
||||||
log.Infof("stopped proxying from remote peer %s due to closed connection", p.remoteKey)
|
log.Debugf("stopped proxying from remote peer %s due to closed connection", p.remoteKey)
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
n, err := remoteConn.Read(buf)
|
n, err := remoteConn.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//log.Errorf("failed reading from remote connection %s", err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = p.wgConn.Write(buf[:n])
|
_, err = p.wgConn.Write(buf[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//log.Errorf("failed writing to local Wireguard instance %s", err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var version = "development"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
|
cmd.Version = version
|
||||||
if err := cmd.Execute(); err != nil {
|
if err := cmd.Execute(); err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
104
docs/README.md
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
### Table of contents
|
||||||
|
|
||||||
|
* [About Wiretrustee](#about-wiretrustee)
|
||||||
|
* [Why Wireguard with Wiretrustee?](#why-wireguard-with-wiretrustee)
|
||||||
|
* [Wiretrustee vs. Traditional VPN](#wiretrustee-vs-traditional-vpn)
|
||||||
|
* [High-level technology overview](#high-level-technology-overview)
|
||||||
|
* [Getting started](#getting-started)
|
||||||
|
|
||||||
|
### About Wiretrustee
|
||||||
|
|
||||||
|
Wiretrustee is an open-source VPN platform built on top of [WireGuard®](https://www.wireguard.com/) making it easy to create secure private networks for your organization or home.
|
||||||
|
|
||||||
|
It requires zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, vpn gateways, and so forth.
|
||||||
|
|
||||||
|
There is no centralized VPN server with Wiretrustee - your computers, devices, machines, and servers connect to each other directly over a fast encrypted tunnel.
|
||||||
|
|
||||||
|
It literally takes less than 5 minutes to provision a secure peer-to-peer VPN with Wiretrustee. Check our [Quickstart Guide Video](https://www.youtube.com/watch?v=cWTsGUJAUaU) to see the setup in action.
|
||||||
|
|
||||||
|
### Why Wireguard with Wiretrustee?
|
||||||
|
|
||||||
|
WireGuard is a modern and extremely fast VPN tunnel utilizing state-of-the-art [cryptography](https://www.wireguard.com/protocol/)
|
||||||
|
and Wiretrustee uses Wireguard to establish a secure tunnel between machines.
|
||||||
|
|
||||||
|
Built with simplicity in mind, Wireguard ensures that traffic between two machines is encrypted and flowing, however, it requires a few things to be done beforehand.
|
||||||
|
|
||||||
|
First, in order to connect, the machines have to be configured.
|
||||||
|
On each machine, you need to generate private and public keys and prepare a WireGuard configuration file.
|
||||||
|
The configuration also includes a private IP address that should be unique per machine.
|
||||||
|
|
||||||
|
Secondly, to accept the incoming traffic, the machines have to trust each other.
|
||||||
|
The generated public keys have to be pre-shared on the machines.
|
||||||
|
This works similarly to SSH with its authorised_keys file.
|
||||||
|
|
||||||
|
Lastly, the connectivity between the machines has to be ensured.
|
||||||
|
To make machines reach one another, you are required to set a WireGuard endpoint property which indicates the IP address and port of the remote machine to connect to.
|
||||||
|
On many occasions, machines are hidden behind firewalls and NAT devices,
|
||||||
|
meaning that you may need to configure a port forwarding or open holes in your firewall to ensure the machines are reachable.
|
||||||
|
|
||||||
|
The undertakings mentioned above might not be complicated if you have just a few machines, but the complexity grows as the number of machines increases.
|
||||||
|
|
||||||
|
Wiretrustee simplifies the setup by automatically generating private and public keys, assigning unique private IP addresses, and takes care of sharing public keys between the machines.
|
||||||
|
It is worth mentioning that the private key never leaves the machine.
|
||||||
|
So only the machine that owns the key can decrypt traffic addressed to it.
|
||||||
|
The same applies also to the relayed traffic mentioned below.
|
||||||
|
|
||||||
|
Furthermore, Wiretrustee ensures connectivity by leveraging advanced [NAT traversal techniques](https://en.wikipedia.org/wiki/NAT_traversal)
|
||||||
|
and removing the necessity of port forwarding, opening holes in the firewall, and having a public static IP address.
|
||||||
|
In cases when a direct peer-to-peer connection isn't possible, all traffic is relayed securely between peers.
|
||||||
|
Wiretrustee also monitors the connection health and restarts broken connections.
|
||||||
|
|
||||||
|
There are a few more things that we are working on to make secure private networks simple. A few examples are ACLs, MFA and activity monitoring.
|
||||||
|
|
||||||
|
Check out the WireGuard [Quick Start](https://www.wireguard.com/quickstart/) guide to learn more about configuring "plain" WireGuard without Wiretrustee.
|
||||||
|
|
||||||
|
### Wiretrustee vs. Traditional VPN
|
||||||
|
|
||||||
|
In the traditional VPN model, everything converges on a centralized, protected network where all the clients are connecting to a central VPN server.
|
||||||
|
|
||||||
|
An increasing amount of connections can easily overload the VPN server.
|
||||||
|
Even a short downtime of a server can cause expensive system disruptions, and a remote team's inability to work.
|
||||||
|
|
||||||
|
Centralized VPNs imply all the traffic going through the central server causing network delays and increased traffic usage.
|
||||||
|
|
||||||
|
Such systems require an experienced team to set up and maintain.
|
||||||
|
Configuring firewalls, setting up NATs, SSO integration, and managing access control lists can be a nightmare.
|
||||||
|
|
||||||
|
Traditional centralized VPNs are often compared to a [castle-and-moat](https://en.wikipedia.org/wiki/Moat) model
|
||||||
|
in which once accessed, user is trusted and can access critical infrastructure and resources without any restrictions.
|
||||||
|
|
||||||
|
Wiretrustee decentralizes networks using direct point-to-point connections, as opposed to traditional models.
|
||||||
|
Consequently, network performance is increased since traffic flows directly between the machines bypassing VPN servers or gateways.
|
||||||
|
To achieve this, Wiretrustee client applications employ signalling servers to find other machines and negotiate connections.
|
||||||
|
These are similar to the signaling servers used in [WebRTC](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling#the_signaling_server)
|
||||||
|
|
||||||
|
Thanks to [NAT traversal techniques](https://en.wikipedia.org/wiki/NAT_traversal),
|
||||||
|
outlined in the [Why not just Wireguard?](#why-wireguard-with-wiretrustee) section above,
|
||||||
|
Wiretrustee installation doesn't require complex network and firewall configuration.
|
||||||
|
It just works, minimising the maintenance effort.
|
||||||
|
|
||||||
|
Finally, each machine or device in the Wiretrustee network verifies incoming connections accepting only the trusted ones.
|
||||||
|
This is ensured by Wireguard's [Crypto Routing concept](https://www.wireguard.com/#cryptokey-routing).
|
||||||
|
|
||||||
|
### High-level technology overview
|
||||||
|
In essence, Wiretrustee is an open source platform consisting of a collection of systems, responsible for handling peer-to-peer connections, tunneling and network management (IP, keys, ACLs, etc).
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="media/high-level-dia.png" alt="high-level-dia" width="781"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
Wiretrustee uses open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [Coturn](https://github.com/coturn/coturn),
|
||||||
|
and [software](https://github.com/wiretrustee/wiretrustee) developed by Wiretrustee authors to make it all work together.
|
||||||
|
|
||||||
|
To learn more about Wiretrustee architecture, please refer to the [architecture section](../docs/architecture.md).
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
|
||||||
|
There are 2 ways of getting started with Wiretrustee:
|
||||||
|
- use Cloud Managed version
|
||||||
|
- self-hosting
|
||||||
|
|
||||||
|
We recommend starting with the cloud managed version hosted at [app.wiretrustee.com](https://app.wiretrustee.com) - the quickest way to get familiar with the system.
|
||||||
|
See [Quickstart Guide](../docs/quickstart.md) for instructions.
|
||||||
|
|
||||||
|
If you don't want to use the managed version, check out our [Self-hosting Guide](../docs/self-hosting.md).
|
||||||
2
docs/architecture.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
### Architecture
|
||||||
|
TODO
|
||||||
BIN
docs/media/add-peer.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
docs/media/auth.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/media/empty-peers.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/media/high-level-dia.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
docs/media/logo-full.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/media/logo.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/media/peerA.gif
Normal file
|
After Width: | Height: | Size: 409 KiB |
BIN
docs/media/peerB.gif
Normal file
|
After Width: | Height: | Size: 526 KiB |
|
Before Width: | Height: | Size: 5.9 MiB After Width: | Height: | Size: 5.9 MiB |
BIN
docs/media/peers.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
41
docs/quickstart.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
## Quickstart guide (Cloud Managed version)
|
||||||
|
Step-by-step video guide on YouTube:
|
||||||
|
|
||||||
|
[](https://youtu.be/cWTsGUJAUaU "Wiretrustee - secure private network in less than 5 minutes")
|
||||||
|
|
||||||
|
This guide describes how to create secure VPN and connect 2 machines peer-to-peer.
|
||||||
|
|
||||||
|
One machine is a Raspberry Pi Compute Module 4 hosted at home (Peer A), and the other one is a regular Ubuntu server running in the Data Center (Peer B).
|
||||||
|
Both machines are running Linux (Raspbian and Ubuntu respectively), but you could also use Mac or Windows operating systems.
|
||||||
|
|
||||||
|
1. Sign-up at [https://app.wiretrustee.com/](https://app.wiretrustee.com/peers)
|
||||||
|
|
||||||
|
You can use your email and password to sign-up or any available social login option (e.g., GitHub account)
|
||||||
|
|
||||||
|
<img src="media/auth.png" alt="auth" width="350"/>
|
||||||
|
|
||||||
|
2. After a successful login you will be redirected to the ```Peers``` screen which is empty because you don't have any peers yet.
|
||||||
|
|
||||||
|
Click ```Add peer``` to add a new machine.
|
||||||
|
|
||||||
|
<img src="media/empty-peers.png" alt="empty-peers" width="700"/>
|
||||||
|
|
||||||
|
3. Choose a setup key which will be used to associate your new machine with your account (in our case it is ```Default key```).
|
||||||
|
|
||||||
|
Choose your machine operating system (in our case it is ```Linux```) and proceed with the installation steps on the machine.
|
||||||
|
|
||||||
|
<img src="media/add-peer.png" alt="add-peer" width="700"/>
|
||||||
|
|
||||||
|
4. Repeat #3 for the 2nd machine.
|
||||||
|
5. Return to ```Peers``` and you should notice 2 new machines with status ```Connected```
|
||||||
|
|
||||||
|
<img src="media/peers.png" alt="peers" width="700"/>
|
||||||
|
|
||||||
|
6. To test the connection you could try pinging devices:
|
||||||
|
|
||||||
|
On Peer A:
|
||||||
|
```ping 100.64.0.2```
|
||||||
|
|
||||||
|
On Peer B:
|
||||||
|
```ping 100.64.0.1```
|
||||||
|
7. Done! You now have a secure peer-to-peer VPN configured.
|
||||||
96
docs/self-hosting.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
### Self-hosting
|
||||||
|
Wiretrustee is an open-source platform that can be self-hosted on your servers.
|
||||||
|
|
||||||
|
It relies on components developed by Wiretrustee Authors [Management Service](https://github.com/wiretrustee/wiretrustee/tree/main/management), [Management UI Dashboard](https://github.com/wiretrustee/wiretrustee-dashboard), [Signal Service](https://github.com/wiretrustee/wiretrustee/tree/main/signal),
|
||||||
|
a 3rd party open-source STUN/TURN service [Coturn](https://github.com/coturn/coturn) and a 3rd party service [Auth0](https://auth0.com/).
|
||||||
|
|
||||||
|
All the components can be self-hosted except for the Auth0 service.
|
||||||
|
We chose Auth0 to "outsource" the user management part of the platform because we believe that implementing a proper user auth requires significant amount of time to make it right.
|
||||||
|
We focused on connectivity instead.
|
||||||
|
|
||||||
|
If you would like to learn more about the architecture please refer to the [Wiretrustee Architecture section](architecture.md).
|
||||||
|
|
||||||
|
### Step-by-step video guide on YouTube:
|
||||||
|
|
||||||
|
[](https://youtu.be/Ofpgx5WhT0k "Wiretrustee Self-Hosting Guide")
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- Virtual machine offered by any cloud provider (e.g., AWS, DigitalOcean, Hetzner, Google Cloud, Azure ...).
|
||||||
|
- Any Linux OS.
|
||||||
|
- Docker Compose installed (see [Install Docker Compose](https://docs.docker.com/compose/install/)).
|
||||||
|
- Domain name pointing to the public IP address of your server.
|
||||||
|
- Open ports ```443, 33071, 33073, 10000, 3478``` (Dashboard, Management HTTP API, Management gRpc API, Signal gRpc, Coturn STUN/TURN respectively) on your server.
|
||||||
|
- Maybe a cup of coffee or tea :)
|
||||||
|
|
||||||
|
### Step-by-step guide
|
||||||
|
|
||||||
|
For this tutorial we will be using domain ```test.wiretrustee.com``` which points to our Ubuntu 20.04 machine hosted at Hetzner.
|
||||||
|
|
||||||
|
1. Create Auth0 account at [auth0.com](https://auth0.com/).
|
||||||
|
2. Login to your server, clone Wiretrustee repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/wiretrustee/wiretrustee.git wiretrustee/
|
||||||
|
```
|
||||||
|
|
||||||
|
and switch to the ```wiretrustee/infrastructure_files/``` folder that contains docker compose file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd wiretrustee/infrastructure_files/
|
||||||
|
```
|
||||||
|
3. Prepare configuration files.
|
||||||
|
|
||||||
|
To simplify the setup we have prepared a script to substitute required properties in the [docker-compose.yml.tmpl](../infrastructure_files/docker-compose.yml.tmpl) and [management.json.tmpl](../infrastructure_files/management.json.tmpl) files.
|
||||||
|
|
||||||
|
The [setup.env](../infrastructure_files/setup.env) file contains the following properties that have to be filled:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# e.g. app.mydomain.com
|
||||||
|
WIRETRUSTEE_DOMAIN=""
|
||||||
|
# e.g. dev-24vkclam.us.auth0.com
|
||||||
|
WIRETRUSTEE_AUTH0_DOMAIN=""
|
||||||
|
# e.g. 61u3JMXRO0oOevc7gCkZLCwePQvT4lL0
|
||||||
|
WIRETRUSTEE_AUTH0_CLIENT_ID=""
|
||||||
|
# e.g. https://app.mydomain.com/
|
||||||
|
WIRETRUSTEE_AUTH0_AUDIENCE=""
|
||||||
|
# e.g. hello@mydomain.com
|
||||||
|
WIRETRUSTEE_LETSENCRYPT_EMAIL=""
|
||||||
|
```
|
||||||
|
|
||||||
|
Please follow the steps to get the values.
|
||||||
|
|
||||||
|
4. Configure ```WIRETRUSTEE_AUTH0_DOMAIN``` ```WIRETRUSTEE_AUTH0_CLIENT_ID``` ```WIRETRUSTEE_AUTH0_AUDIENCE``` properties.
|
||||||
|
|
||||||
|
* To obtain these, please use [Auth0 React SDK Guide](https://auth0.com/docs/quickstart/spa/react/01-login#configure-auth0) up until "Install the Auth0 React SDK".
|
||||||
|
|
||||||
|
:grey_exclamation: Use ```https://YOUR DOMAIN``` as ````Allowed Callback URLs````, ```Allowed Logout URLs```, ```Allowed Web Origins``` and ```Allowed Origins (CORS)```
|
||||||
|
* set the variables in the ```setup.env```
|
||||||
|
5. Configure ```WIRETRUSTEE_AUTH0_AUDIENCE``` property.
|
||||||
|
|
||||||
|
* Check [Auth0 Golang API Guide](https://auth0.com/docs/quickstart/backend/golang) to obtain AuthAudience.
|
||||||
|
* set the property in the ```setup.env``` file.
|
||||||
|
6. Configure ```WIRETRUSTEE_LETSENCRYPT_EMAIL``` property.
|
||||||
|
|
||||||
|
This can be any email address. [Let's Encrypt](https://letsencrypt.org/) will create an account while generating a new certificate.
|
||||||
|
|
||||||
|
7. Make sure all the properties set in the ```setup.env``` file and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./configure.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This will export all the properties as environment variables and generate ```docker-compose.yml``` and ```management.json``` files substituting required variables.
|
||||||
|
|
||||||
|
8. Run docker compose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
9. Optionally check the logs by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose logs signal
|
||||||
|
docker-compose logs management
|
||||||
|
docker-compose logs coturn
|
||||||
|
docker-compose logs dashboard
|
||||||
3
go.mod
@@ -8,13 +8,14 @@ require (
|
|||||||
github.com/golang/protobuf v1.5.2
|
github.com/golang/protobuf v1.5.2
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/kardianos/service v1.2.0
|
github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7
|
||||||
github.com/onsi/ginkgo v1.16.4
|
github.com/onsi/ginkgo v1.16.4
|
||||||
github.com/onsi/gomega v1.13.0
|
github.com/onsi/gomega v1.13.0
|
||||||
github.com/pion/ice/v2 v2.1.7
|
github.com/pion/ice/v2 v2.1.7
|
||||||
github.com/rs/cors v1.8.0
|
github.com/rs/cors v1.8.0
|
||||||
github.com/sirupsen/logrus v1.7.0
|
github.com/sirupsen/logrus v1.7.0
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.1.3
|
||||||
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/vishvananda/netlink v1.1.0
|
github.com/vishvananda/netlink v1.1.0
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||||
|
|||||||
4
go.sum
@@ -146,8 +146,8 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
|
|||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
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/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/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.1-0.20210728001519-a323c3813bc7 h1:oohm9Rk9JAxxmp2NLZa7Kebgz9h4+AJDcc64txg3dQ0=
|
||||||
github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ func GetListenPort(iface string) (*int, error) {
|
|||||||
|
|
||||||
// UpdatePeer 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
|
// Endpoint is optional
|
||||||
func UpdatePeer(iface string, peerKey string, allowedIps string, keepAlive time.Duration, endpoint string) error {
|
func UpdatePeer(iface string, peerKey string, allowedIps string, keepAlive time.Duration, endpoint string, preSharedKey *wgtypes.Key) error {
|
||||||
|
|
||||||
log.Debugf("updating interface %s peer %s: endpoint %s ", iface, peerKey, endpoint)
|
log.Debugf("updating interface %s peer %s: endpoint %s ", iface, peerKey, endpoint)
|
||||||
|
|
||||||
@@ -165,6 +165,7 @@ func UpdatePeer(iface string, peerKey string, allowedIps string, keepAlive time.
|
|||||||
ReplaceAllowedIPs: true,
|
ReplaceAllowedIPs: true,
|
||||||
AllowedIPs: []net.IPNet{*ipNet},
|
AllowedIPs: []net.IPNet{*ipNet},
|
||||||
PersistentKeepaliveInterval: &keepAlive,
|
PersistentKeepaliveInterval: &keepAlive,
|
||||||
|
PresharedKey: preSharedKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
config := wgtypes.Config{
|
config := wgtypes.Config{
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ func Test_UpdatePeer(t *testing.T) {
|
|||||||
keepAlive := 15 * time.Second
|
keepAlive := 15 * time.Second
|
||||||
allowedIP := "10.99.99.2/32"
|
allowedIP := "10.99.99.2/32"
|
||||||
endpoint := "127.0.0.1:9900"
|
endpoint := "127.0.0.1:9900"
|
||||||
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
|
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ func Test_UpdatePeerEndpoint(t *testing.T) {
|
|||||||
keepAlive := 15 * time.Second
|
keepAlive := 15 * time.Second
|
||||||
allowedIP := "10.99.99.2/32"
|
allowedIP := "10.99.99.2/32"
|
||||||
endpoint := "127.0.0.1:9900"
|
endpoint := "127.0.0.1:9900"
|
||||||
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
|
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@ func Test_RemovePeer(t *testing.T) {
|
|||||||
keepAlive := 15 * time.Second
|
keepAlive := 15 * time.Second
|
||||||
allowedIP := "10.99.99.2/32"
|
allowedIP := "10.99.99.2/32"
|
||||||
endpoint := "127.0.0.1:9900"
|
endpoint := "127.0.0.1:9900"
|
||||||
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
|
err = UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
7
infrastructure_files/configure.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
unset $(grep -v '^#' ./setup.env | sed -E 's/(.*)=.*/\1/' | xargs)
|
||||||
|
export $(grep -v '^#' ./setup.env | xargs)
|
||||||
|
|
||||||
|
envsubst < docker-compose.yml.tmpl > docker-compose.yml
|
||||||
|
envsubst < management.json.tmpl > management.json
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
version: "3"
|
|
||||||
services:
|
|
||||||
#UI dashboard
|
|
||||||
dashboard:
|
|
||||||
image: wiretrustee/dashboard:main
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- 80:80
|
|
||||||
# - 443:443
|
|
||||||
environment:
|
|
||||||
- AUTH0_DOMAIN=<YOUR AUTH0 DOMAIN>
|
|
||||||
- AUTH0_CLIENT_ID=<YOUR AUTH0 CLIENT ID>
|
|
||||||
- AUTH0_AUDIENCE=<YOUR AUTH0 AUDIENCE>
|
|
||||||
- WIRETRUSTEE_MGMT_API_ENDPOINT=http://localhost:33071
|
|
||||||
# - NGINX_SSL_PORT: 443
|
|
||||||
# - LETSENCRYPT_DOMAIN: <YOUR DOMAIN>
|
|
||||||
# - LETSENCRYPT_EMAIL: <YOUR EMAIL>
|
|
||||||
# Signal
|
|
||||||
signal:
|
|
||||||
image: wiretrustee/signal:latest
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- wiretrustee-mgmt:/var/lib/wiretrustee
|
|
||||||
- /varl/log/wiretrustee/signal.log:/var/log/wiretrustee/signal.log
|
|
||||||
ports:
|
|
||||||
- 10000:10000
|
|
||||||
# # port and command for Let's Encrypt validation
|
|
||||||
# - 443:443
|
|
||||||
# command: ["--letsencrypt-domain", "<YOUR-DOMAIN>", "--log-file", "console"]
|
|
||||||
# Management
|
|
||||||
management:
|
|
||||||
image: wiretrustee/management:latest
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- wiretrustee-mgmt:/var/lib/wiretrustee
|
|
||||||
- ./management.json:/etc/wiretrustee/management.json
|
|
||||||
# - /var/log/wiretrustee/management.log:/var/log/wiretrustee/management.log
|
|
||||||
ports:
|
|
||||||
- 33073:33073 #gRPC port
|
|
||||||
- 33071:33071 #HTTP port
|
|
||||||
# # port and command for Let's Encrypt validation
|
|
||||||
# - 443:443
|
|
||||||
# command: ["--letsencrypt-domain", "<YOUR-DOMAIN>", "--log-file", "console"]
|
|
||||||
# Coturn
|
|
||||||
coturn:
|
|
||||||
image: coturn/coturn
|
|
||||||
restart: unless-stopped
|
|
||||||
domainname: stun.wiretrustee.com
|
|
||||||
volumes:
|
|
||||||
- ./turnserver.conf:/etc/turnserver.conf:ro
|
|
||||||
# - ./privkey.pem:/etc/coturn/private/privkey.pem:ro
|
|
||||||
# - ./cert.pem:/etc/coturn/certs/cert.pem:ro
|
|
||||||
network_mode: host
|
|
||||||
volumes:
|
|
||||||
wiretrustee-mgmt:
|
|
||||||
wiretrustee-signal:
|
|
||||||
61
infrastructure_files/docker-compose.yml.tmpl
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
#UI dashboard
|
||||||
|
dashboard:
|
||||||
|
image: wiretrustee/dashboard:main
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 443:443
|
||||||
|
environment:
|
||||||
|
- AUTH0_DOMAIN=$WIRETRUSTEE_AUTH0_DOMAIN
|
||||||
|
- AUTH0_CLIENT_ID=$WIRETRUSTEE_AUTH0_CLIENT_ID
|
||||||
|
- AUTH0_AUDIENCE=$WIRETRUSTEE_AUTH0_AUDIENCE
|
||||||
|
- WIRETRUSTEE_MGMT_API_ENDPOINT=https://$WIRETRUSTEE_DOMAIN:33071
|
||||||
|
- NGINX_SSL_PORT=443
|
||||||
|
- LETSENCRYPT_DOMAIN=$WIRETRUSTEE_DOMAIN
|
||||||
|
- LETSENCRYPT_EMAIL=$WIRETRUSTEE_LETSENCRYPT_EMAIL
|
||||||
|
volumes:
|
||||||
|
- /var/lib/wiretrustee/dashboard/letsencrypt:/etc/letsencrypt/
|
||||||
|
# Signal
|
||||||
|
signal:
|
||||||
|
image: wiretrustee/signal:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- wiretrustee-signal:/var/lib/wiretrustee
|
||||||
|
# - /var/log/wiretrustee/signal.log:/var/log/wiretrustee/signal.log
|
||||||
|
ports:
|
||||||
|
- 10000:10000
|
||||||
|
# # port and command for Let's Encrypt validation
|
||||||
|
# - 443:443
|
||||||
|
# command: ["--letsencrypt-domain", "$WIRETRUSTEE_DOMAIN", "--log-file", "console"]
|
||||||
|
# Management
|
||||||
|
management:
|
||||||
|
image: wiretrustee/management:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- dashboard
|
||||||
|
volumes:
|
||||||
|
- wiretrustee-mgmt:/var/lib/wiretrustee
|
||||||
|
- /var/lib/wiretrustee/dashboard/letsencrypt:/etc/letsencrypt:ro
|
||||||
|
- ./management.json:/etc/wiretrustee/management.json
|
||||||
|
# - /var/log/wiretrustee/management.log:/var/log/wiretrustee/management.log
|
||||||
|
ports:
|
||||||
|
- 33073:33073 #gRPC port
|
||||||
|
- 33071:33071 #HTTP port
|
||||||
|
# # port and command for Let's Encrypt validation
|
||||||
|
# - 443:443
|
||||||
|
# command: ["--letsencrypt-domain", "$WIRETRUSTEE_DOMAIN", "--log-file", "console"]
|
||||||
|
# Coturn
|
||||||
|
coturn:
|
||||||
|
image: coturn/coturn
|
||||||
|
restart: unless-stopped
|
||||||
|
domainname: <YOUR DOMAIN>
|
||||||
|
volumes:
|
||||||
|
- ./turnserver.conf:/etc/turnserver.conf:ro
|
||||||
|
# - ./privkey.pem:/etc/coturn/private/privkey.pem:ro
|
||||||
|
# - ./cert.pem:/etc/coturn/certs/cert.pem:ro
|
||||||
|
network_mode: host
|
||||||
|
volumes:
|
||||||
|
wiretrustee-mgmt:
|
||||||
|
wiretrustee-signal:
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"Stuns": [
|
|
||||||
{
|
|
||||||
"Proto": "udp",
|
|
||||||
"URI": "stun:stun.wiretrustee.com:3468",
|
|
||||||
"Username": "",
|
|
||||||
"Password": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"TURNConfig": {
|
|
||||||
"Turns": [
|
|
||||||
{
|
|
||||||
"Proto": "udp",
|
|
||||||
"URI": "turn:stun.wiretrustee.com:3468",
|
|
||||||
"Username": "some_user",
|
|
||||||
"Password": "c29tZV9wYXNzd29yZA=="
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"CredentialsTTL": "1h",
|
|
||||||
"Secret": "c29tZV9wYXNzd29yZA==",
|
|
||||||
"TimeBasedCredentials": true
|
|
||||||
},
|
|
||||||
"Signal": {
|
|
||||||
"Proto": "http",
|
|
||||||
"URI": "signal.wiretrustee.com:10000",
|
|
||||||
"Username": "",
|
|
||||||
"Password": null
|
|
||||||
},
|
|
||||||
"Datadir": "",
|
|
||||||
"HttpConfig": {
|
|
||||||
"LetsEncryptDomain": "<PASTE YOUR LET'S ENCRYPT DOMAIN HERE>",
|
|
||||||
"Address": "0.0.0.0:33071",
|
|
||||||
"AuthIssuer": "<PASTE YOUR AUTH0 ISSUER HERE>,",
|
|
||||||
"AuthAudience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
|
|
||||||
"AuthKeysLocation": "<PASTE YOUR AUTH0 PUBLIC JWT KEYS LOCATION HERE>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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=""
|
||||||
@@ -14,7 +14,8 @@ Flags:
|
|||||||
-h, --help help for management
|
-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
|
--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)
|
--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:
|
Global Flags:
|
||||||
--config string Wiretrustee config file location to write new config to (default "/etc/wiretrustee/config.json")
|
--config string Wiretrustee config file location to write new config to (default "/etc/wiretrustee/config.json")
|
||||||
--log-level string (default "info")
|
--log-level string (default "info")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/wiretrustee/wiretrustee/client/system"
|
"github.com/wiretrustee/wiretrustee/client/system"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
"github.com/wiretrustee/wiretrustee/management/proto"
|
"github.com/wiretrustee/wiretrustee/management/proto"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
"io"
|
"io"
|
||||||
@@ -32,7 +34,7 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE
|
|||||||
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
mgmCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
mgmCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
conn, err := grpc.DialContext(
|
conn, err := grpc.DialContext(
|
||||||
mgmCtx,
|
mgmCtx,
|
||||||
@@ -40,12 +42,12 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE
|
|||||||
transportOption,
|
transportOption,
|
||||||
grpc.WithBlock(),
|
grpc.WithBlock(),
|
||||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||||
Time: 3 * time.Second,
|
Time: 15 * time.Second,
|
||||||
Timeout: 2 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed creating connection to Management Srvice %v", err)
|
log.Errorf("failed creating connection to Management Service %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,26 +67,38 @@ func (c *Client) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//defaultBackoff is a basic backoff mechanism for general issues
|
//defaultBackoff is a basic backoff mechanism for general issues
|
||||||
func defaultBackoff() backoff.BackOff {
|
func defaultBackoff(ctx context.Context) backoff.BackOff {
|
||||||
return &backoff.ExponentialBackOff{
|
return backoff.WithContext(&backoff.ExponentialBackOff{
|
||||||
InitialInterval: 800 * time.Millisecond,
|
InitialInterval: 800 * time.Millisecond,
|
||||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||||
Multiplier: backoff.DefaultMultiplier,
|
Multiplier: backoff.DefaultMultiplier,
|
||||||
MaxInterval: 30 * time.Second,
|
MaxInterval: 10 * time.Second,
|
||||||
MaxElapsedTime: 24 * 3 * time.Hour, //stop after 3 days trying
|
MaxElapsedTime: 12 * time.Hour, //stop after 12 hours of trying, the error will be propagated to the general retry of the client
|
||||||
Stop: backoff.Stop,
|
Stop: backoff.Stop,
|
||||||
Clock: backoff.SystemClock,
|
Clock: backoff.SystemClock,
|
||||||
}
|
}, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ready indicates whether the client is okay and ready to be used
|
||||||
|
// for now it just checks whether gRPC connection to the service is ready
|
||||||
|
func (c *Client) ready() bool {
|
||||||
|
return c.conn.GetState() == connectivity.Ready
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync wraps the real client's Sync endpoint call and takes care of retries and encryption/decryption of messages
|
// 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
|
// Blocking request. The result will be sent via msgHandler callback function
|
||||||
func (c *Client) Sync(msgHandler func(msg *proto.SyncResponse) error) error {
|
func (c *Client) Sync(msgHandler func(msg *proto.SyncResponse) error) error {
|
||||||
|
|
||||||
var backOff = defaultBackoff()
|
var backOff = defaultBackoff(c.ctx)
|
||||||
|
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
|
|
||||||
|
log.Debugf("management connection state %v", c.conn.GetState())
|
||||||
|
|
||||||
|
if !c.ready() {
|
||||||
|
return fmt.Errorf("no connection to management")
|
||||||
|
}
|
||||||
|
|
||||||
// todo we already have it since we did the Login, maybe cache it locally?
|
// todo we already have it since we did the Login, maybe cache it locally?
|
||||||
serverPubKey, err := c.GetServerPublicKey()
|
serverPubKey, err := c.GetServerPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -98,23 +112,21 @@ func (c *Client) Sync(msgHandler func(msg *proto.SyncResponse) error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("connected to the Management Service Stream")
|
log.Infof("connected to the Management Service stream")
|
||||||
|
|
||||||
// blocking until error
|
// blocking until error
|
||||||
err = c.receiveEvents(stream, *serverPubKey, msgHandler)
|
err = c.receiveEvents(stream, *serverPubKey, msgHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
/*if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.PermissionDenied {
|
backOff.Reset()
|
||||||
//todo handle differently??
|
|
||||||
}*/
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
backOff.Reset()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := backoff.Retry(operation, backOff)
|
err := backoff.Retry(operation, backOff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("exiting Management Service connection retry loop due to unrecoverable error %s ", err)
|
log.Warnf("exiting Management Service connection retry loop due to unrecoverable error: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,11 +153,11 @@ func (c *Client) receiveEvents(stream proto.ManagementService_SyncClient, server
|
|||||||
for {
|
for {
|
||||||
update, err := stream.Recv()
|
update, err := stream.Recv()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
log.Errorf("managment stream was closed: %s", err)
|
log.Errorf("Management stream has been closed by server: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("disconnected from Management Service sync stream: %v", err)
|
log.Warnf("disconnected from Management Service sync stream: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +171,7 @@ func (c *Client) receiveEvents(stream proto.ManagementService_SyncClient, server
|
|||||||
|
|
||||||
err = msgHandler(decryptedResp)
|
err = msgHandler(decryptedResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed handling an update message received from Management Service %v", err.Error())
|
log.Errorf("failed handling an update message received from Management Service: %v", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,6 +179,10 @@ func (c *Client) receiveEvents(stream proto.ManagementService_SyncClient, server
|
|||||||
|
|
||||||
// GetServerPublicKey returns server Wireguard public key (used later for encrypting messages sent to the server)
|
// GetServerPublicKey returns server Wireguard public key (used later for encrypting messages sent to the server)
|
||||||
func (c *Client) GetServerPublicKey() (*wgtypes.Key, error) {
|
func (c *Client) GetServerPublicKey() (*wgtypes.Key, error) {
|
||||||
|
if !c.ready() {
|
||||||
|
return nil, fmt.Errorf("no connection to management")
|
||||||
|
}
|
||||||
|
|
||||||
mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second) //todo make a general setting
|
mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second) //todo make a general setting
|
||||||
defer cancel()
|
defer cancel()
|
||||||
resp, err := c.realClient.GetServerKey(mgmCtx, &proto.Empty{})
|
resp, err := c.realClient.GetServerKey(mgmCtx, &proto.Empty{})
|
||||||
@@ -183,6 +199,9 @@ func (c *Client) GetServerPublicKey() (*wgtypes.Key, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*proto.LoginResponse, error) {
|
func (c *Client) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*proto.LoginResponse, error) {
|
||||||
|
if !c.ready() {
|
||||||
|
return nil, fmt.Errorf("no connection to management")
|
||||||
|
}
|
||||||
loginReq, err := encryption.EncryptMessage(serverKey, c.key, req)
|
loginReq, err := encryption.EncryptMessage(serverKey, c.key, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to encrypt message: %s", err)
|
log.Errorf("failed to encrypt message: %s", err)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/wiretrustee/wiretrustee/management/server"
|
"github.com/wiretrustee/wiretrustee/management/server"
|
||||||
@@ -25,6 +26,8 @@ var (
|
|||||||
mgmtDataDir string
|
mgmtDataDir string
|
||||||
mgmtConfig string
|
mgmtConfig string
|
||||||
mgmtLetsencryptDomain string
|
mgmtLetsencryptDomain string
|
||||||
|
certFile string
|
||||||
|
certKey string
|
||||||
|
|
||||||
kaep = keepalive.EnforcementPolicy{
|
kaep = keepalive.EnforcementPolicy{
|
||||||
MinTime: 15 * time.Second,
|
MinTime: 15 * time.Second,
|
||||||
@@ -71,12 +74,23 @@ var (
|
|||||||
|
|
||||||
var httpServer *http.Server
|
var httpServer *http.Server
|
||||||
if config.HttpConfig.LetsEncryptDomain != "" {
|
if config.HttpConfig.LetsEncryptDomain != "" {
|
||||||
|
//automatically generate a new certificate with Let's Encrypt
|
||||||
certManager := encryption.CreateCertManager(config.Datadir, config.HttpConfig.LetsEncryptDomain)
|
certManager := encryption.CreateCertManager(config.Datadir, config.HttpConfig.LetsEncryptDomain)
|
||||||
transportCredentials := credentials.NewTLS(certManager.TLSConfig())
|
transportCredentials := credentials.NewTLS(certManager.TLSConfig())
|
||||||
opts = append(opts, grpc.Creds(transportCredentials))
|
opts = append(opts, grpc.Creds(transportCredentials))
|
||||||
|
|
||||||
httpServer = http.NewHttpsServer(config.HttpConfig, certManager, accountManager)
|
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 {
|
} else {
|
||||||
|
//start server without SSL
|
||||||
httpServer = http.NewHttpServer(config.HttpConfig, accountManager)
|
httpServer = http.NewHttpServer(config.HttpConfig, accountManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,14 +150,37 @@ func loadConfig() (*server.Config, error) {
|
|||||||
config.Datadir = mgmtDataDir
|
config.Datadir = mgmtDataDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if certKey != "" && certFile != "" {
|
||||||
|
config.HttpConfig.CertFile = certFile
|
||||||
|
config.HttpConfig.CertKey = certKey
|
||||||
|
}
|
||||||
|
|
||||||
return config, err
|
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() {
|
func init() {
|
||||||
mgmtCmd.Flags().IntVar(&mgmtPort, "port", 33073, "server port to listen on")
|
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(&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(&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(&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
|
rootCmd.MarkFlagRequired("config") //nolint
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package server
|
|||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/wiretrustee/wiretrustee/util"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccountManager struct {
|
type AccountManager struct {
|
||||||
@@ -35,16 +35,21 @@ func NewManager(store Store, peersUpdateManager *PeersUpdateManager) *AccountMan
|
|||||||
}
|
}
|
||||||
|
|
||||||
//AddSetupKey generates a new setup key with a given name and type, and adds it to the specified account
|
//AddSetupKey generates a new setup key with a given name and type, and adds it to the specified account
|
||||||
func (am *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn time.Duration) (*SetupKey, error) {
|
func (am *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn *util.Duration) (*SetupKey, error) {
|
||||||
am.mux.Lock()
|
am.mux.Lock()
|
||||||
defer am.mux.Unlock()
|
defer am.mux.Unlock()
|
||||||
|
|
||||||
|
keyDuration := DefaultSetupKeyDuration
|
||||||
|
if expiresIn != nil {
|
||||||
|
keyDuration = expiresIn.Duration
|
||||||
|
}
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountId)
|
account, err := am.Store.GetAccount(accountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
setupKey := GenerateSetupKey(keyName, keyType, expiresIn)
|
setupKey := GenerateSetupKey(keyName, keyType, keyDuration)
|
||||||
account.SetupKeys[setupKey.Key] = setupKey
|
account.SetupKeys[setupKey.Key] = setupKey
|
||||||
|
|
||||||
err = am.Store.SaveAccount(account)
|
err = am.Store.SaveAccount(account)
|
||||||
|
|||||||
@@ -36,7 +36,11 @@ type TURNConfig struct {
|
|||||||
// HttpServerConfig is a config of the HTTP Management service server
|
// HttpServerConfig is a config of the HTTP Management service server
|
||||||
type HttpServerConfig struct {
|
type HttpServerConfig struct {
|
||||||
LetsEncryptDomain string
|
LetsEncryptDomain string
|
||||||
Address string
|
//CertFile is the location of the certificate
|
||||||
|
CertFile string
|
||||||
|
//CertKey is the location of the certificate private key
|
||||||
|
CertKey string
|
||||||
|
Address string
|
||||||
// AuthAudience identifies the recipients that the JWT is intended for (aud in JWT)
|
// AuthAudience identifies the recipients that the JWT is intended for (aud in JWT)
|
||||||
AuthAudience string
|
AuthAudience string
|
||||||
// AuthIssuer identifies principal that issued the JWT.
|
// AuthIssuer identifies principal that issued the JWT.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/wiretrustee/wiretrustee/management/server"
|
"github.com/wiretrustee/wiretrustee/management/server"
|
||||||
|
"github.com/wiretrustee/wiretrustee/util"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -34,7 +35,7 @@ type SetupKeyResponse struct {
|
|||||||
type SetupKeyRequest struct {
|
type SetupKeyRequest struct {
|
||||||
Name string
|
Name string
|
||||||
Type server.SetupKeyType
|
Type server.SetupKeyType
|
||||||
ExpiresIn Duration
|
ExpiresIn *util.Duration
|
||||||
Revoked bool
|
Revoked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.R
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, req.Type, req.ExpiresIn.Duration)
|
setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, req.Type, req.ExpiresIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStatus, ok := status.FromError(err)
|
errStatus, ok := status.FromError(err)
|
||||||
if ok && errStatus.Code() == codes.NotFound {
|
if ok && errStatus.Code() == codes.NotFound {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -17,10 +18,11 @@ type Server struct {
|
|||||||
server *http.Server
|
server *http.Server
|
||||||
config *s.HttpServerConfig
|
config *s.HttpServerConfig
|
||||||
certManager *autocert.Manager
|
certManager *autocert.Manager
|
||||||
|
tlsConfig *tls.Config
|
||||||
accountManager *s.AccountManager
|
accountManager *s.AccountManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHttpsServer creates a new HTTPs server (with HTTPS support)
|
// NewHttpsServer creates a new HTTPs server (with HTTPS support) and a certManager that is responsible for generating and renewing Let's Encrypt certificate
|
||||||
// The listening address will be :443 no matter what was specified in s.HttpServerConfig.Address
|
// The listening address will be :443 no matter what was specified in s.HttpServerConfig.Address
|
||||||
func NewHttpsServer(config *s.HttpServerConfig, certManager *autocert.Manager, accountManager *s.AccountManager) *Server {
|
func NewHttpsServer(config *s.HttpServerConfig, certManager *autocert.Manager, accountManager *s.AccountManager) *Server {
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
@@ -32,6 +34,18 @@ func NewHttpsServer(config *s.HttpServerConfig, certManager *autocert.Manager, a
|
|||||||
return &Server{server: server, config: config, certManager: certManager, accountManager: accountManager}
|
return &Server{server: server, config: config, certManager: certManager, accountManager: accountManager}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewHttpsServerWithTLSConfig creates a new HTTPs server with a provided tls.Config.
|
||||||
|
// Usually used when you already have a certificate
|
||||||
|
func NewHttpsServerWithTLSConfig(config *s.HttpServerConfig, tlsConfig *tls.Config, accountManager *s.AccountManager) *Server {
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: config.Address,
|
||||||
|
WriteTimeout: time.Second * 15,
|
||||||
|
ReadTimeout: time.Second * 15,
|
||||||
|
IdleTimeout: time.Second * 60,
|
||||||
|
}
|
||||||
|
return &Server{server: server, config: config, tlsConfig: tlsConfig, accountManager: accountManager}
|
||||||
|
}
|
||||||
|
|
||||||
// NewHttpServer creates a new HTTP server (without HTTPS)
|
// NewHttpServer creates a new HTTP server (without HTTPS)
|
||||||
func NewHttpServer(config *s.HttpServerConfig, accountManager *s.AccountManager) *Server {
|
func NewHttpServer(config *s.HttpServerConfig, accountManager *s.AccountManager) *Server {
|
||||||
return NewHttpsServer(config, nil, accountManager)
|
return NewHttpsServer(config, nil, accountManager)
|
||||||
@@ -71,13 +85,26 @@ func (s *Server) Start() error {
|
|||||||
if s.certManager != nil {
|
if s.certManager != nil {
|
||||||
// if HTTPS is enabled we reuse the listener from the cert manager
|
// if HTTPS is enabled we reuse the listener from the cert manager
|
||||||
listener := s.certManager.Listener()
|
listener := s.certManager.Listener()
|
||||||
log.Infof("http server listening on %s", listener.Addr())
|
log.Infof("HTTPs server listening on %s with Let's Encrypt autocert configured", listener.Addr())
|
||||||
if err = http.Serve(listener, s.certManager.HTTPHandler(r)); err != nil {
|
if err = http.Serve(listener, s.certManager.HTTPHandler(r)); err != nil {
|
||||||
log.Errorf("failed to serve https server: %v", err)
|
log.Errorf("failed to serve https server: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if s.tlsConfig != nil {
|
||||||
|
listener, err := tls.Listen("tcp", s.config.Address, s.tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to serve https server: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("HTTPs server listening on %s", listener.Addr())
|
||||||
|
|
||||||
|
if err = http.Serve(listener, r); err != nil {
|
||||||
|
log.Errorf("failed to serve https server: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.Infof("http server listening on %s", s.server.Addr)
|
log.Infof("HTTP server listening on %s", s.server.Addr)
|
||||||
if err = s.server.ListenAndServe(); err != nil {
|
if err = s.server.ListenAndServe(); err != nil {
|
||||||
log.Errorf("failed to serve http server: %v", err)
|
log.Errorf("failed to serve http server: %v", err)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -12,30 +12,27 @@ fi
|
|||||||
cleanInstall() {
|
cleanInstall() {
|
||||||
printf "\033[32m Post Install of an clean install\033[0m\n"
|
printf "\033[32m Post Install of an clean install\033[0m\n"
|
||||||
# Step 3 (clean install), enable the service in the proper way for this platform
|
# Step 3 (clean install), enable the service in the proper way for this platform
|
||||||
if [ "${use_systemctl}" = "True" ]; then
|
/usr/local/bin/wiretrustee service install
|
||||||
printf "\033[32m Reload the service unit from disk\033[0m\n"
|
/usr/local/bin/wiretrustee service start
|
||||||
systemctl daemon-reload ||:
|
|
||||||
printf "\033[32m Unmask the service\033[0m\n"
|
|
||||||
systemctl unmask wiretrustee ||:
|
|
||||||
printf "\033[32m Set the preset flag for the service unit\033[0m\n"
|
|
||||||
systemctl preset wiretrustee ||:
|
|
||||||
printf "\033[32m Set the enabled flag for the service unit\033[0m\n"
|
|
||||||
systemctl enable wiretrustee ||:
|
|
||||||
systemctl restart wiretrustee ||:
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
upgrade() {
|
upgrade() {
|
||||||
printf "\033[32m Post Install of an upgrade\033[0m\n"
|
printf "\033[32m Post Install of an upgrade\033[0m\n"
|
||||||
if [ "${use_systemctl}" = "True" ]; then
|
if [ "${use_systemctl}" = "True" ]; then
|
||||||
printf "\033[32m Reload the service unit from disk\033[0m\n"
|
printf "\033[32m Stopping the service\033[0m\n"
|
||||||
systemctl daemon-reload ||:
|
systemctl stop wiretrustee 2> /dev/null || true
|
||||||
printf "\033[32m Restarting the service\033[0m\n"
|
|
||||||
systemctl restart wiretrustee ||:
|
|
||||||
fi
|
fi
|
||||||
|
if [ -e /lib/systemd/system/wiretrustee.service ]; then
|
||||||
|
rm -f /lib/systemd/system/wiretrustee.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
fi
|
||||||
|
# will trow an error until everyone upgrade
|
||||||
|
/usr/local/bin/wiretrustee service uninstall 2> /dev/null || true
|
||||||
|
/usr/local/bin/wiretrustee service install
|
||||||
|
/usr/local/bin/wiretrustee service start
|
||||||
}
|
}
|
||||||
|
|
||||||
# Step 2, check if this is a clean install or an upgrade
|
# Check if this is a clean install or an upgrade
|
||||||
action="$1"
|
action="$1"
|
||||||
if [ "$1" = "configure" ] && [ -z "$2" ]; then
|
if [ "$1" = "configure" ] && [ -z "$2" ]; then
|
||||||
# Alpine linux does not pass args, and deb passes $1=configure
|
# Alpine linux does not pass args, and deb passes $1=configure
|
||||||
@@ -50,12 +47,9 @@ case "$action" in
|
|||||||
cleanInstall
|
cleanInstall
|
||||||
;;
|
;;
|
||||||
"2" | "upgrade")
|
"2" | "upgrade")
|
||||||
printf "\033[32m Post Install of an upgrade\033[0m\n"
|
|
||||||
upgrade
|
upgrade
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# $1 == version being installed
|
|
||||||
printf "\033[32m install\033[0m"
|
|
||||||
cleanInstall
|
cleanInstall
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
43
release_files/pre_remove.sh
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# decide if we should use systemd or init/upstart
|
||||||
|
use_systemctl="True"
|
||||||
|
systemd_version=0
|
||||||
|
if ! command -V systemctl >/dev/null 2>&1; then
|
||||||
|
use_systemctl="False"
|
||||||
|
else
|
||||||
|
systemd_version=$(systemctl --version | head -1 | sed 's/systemd //g')
|
||||||
|
fi
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
printf "\033[32m Pre uninstall\033[0m\n"
|
||||||
|
|
||||||
|
if [ "${use_systemctl}" = "True" ]; then
|
||||||
|
printf "\033[32m Stopping the service\033[0m\n"
|
||||||
|
systemctl stop wiretrustee || true
|
||||||
|
|
||||||
|
if [ -e /lib/systemd/system/wiretrustee.service ]; then
|
||||||
|
rm -f /lib/systemd/system/wiretrustee.service
|
||||||
|
systemctl daemon-reload || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
printf "\033[32m Uninstalling the service\033[0m\n"
|
||||||
|
/usr/local/bin/wiretrustee service uninstall || true
|
||||||
|
|
||||||
|
|
||||||
|
if [ "${use_systemctl}" = "True" ]; then
|
||||||
|
printf "\n\033[32m running daemon reload\033[0m\n"
|
||||||
|
systemctl daemon-reload || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
action="$1"
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
"0" | "remove")
|
||||||
|
remove
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"PrivateKey": "",
|
|
||||||
"Peers": [
|
|
||||||
{
|
|
||||||
"WgPubKey": "",
|
|
||||||
"WgAllowedIps": ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"StunTurnURLs": [
|
|
||||||
{
|
|
||||||
"Scheme": 1,
|
|
||||||
"Host": "",
|
|
||||||
"Port": 3468,
|
|
||||||
"Username": "",
|
|
||||||
"Password": "",
|
|
||||||
"Proto": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Scheme": 3,
|
|
||||||
"Host": "",
|
|
||||||
"Port": 3468,
|
|
||||||
"Username": "",
|
|
||||||
"Password": "",
|
|
||||||
"Proto": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"SignalAddr": "",
|
|
||||||
"WgAddr": "",
|
|
||||||
"WgIface": ""
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Wiretrustee Service
|
|
||||||
After=multi-user.target network-online.target
|
|
||||||
Wants=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=/usr/local/bin/wiretrustee up --config /etc/wiretrustee/config.json --log-level debug
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
@@ -23,6 +24,12 @@ import (
|
|||||||
|
|
||||||
// A set of tools to exchange connection details (Wireguard endpoints) with the remote peer.
|
// A set of tools to exchange connection details (Wireguard endpoints) with the remote peer.
|
||||||
|
|
||||||
|
// Status is the status of the client
|
||||||
|
type Status string
|
||||||
|
|
||||||
|
const StreamConnected Status = "Connected"
|
||||||
|
const StreamDisconnected Status = "Disconnected"
|
||||||
|
|
||||||
// Client Wraps the Signal Exchange Service gRpc client
|
// Client Wraps the Signal Exchange Service gRpc client
|
||||||
type Client struct {
|
type Client struct {
|
||||||
key wgtypes.Key
|
key wgtypes.Key
|
||||||
@@ -30,8 +37,15 @@ type Client struct {
|
|||||||
signalConn *grpc.ClientConn
|
signalConn *grpc.ClientConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
stream proto.SignalExchange_ConnectStreamClient
|
stream proto.SignalExchange_ConnectStreamClient
|
||||||
//waiting group to notify once stream is connected
|
// connectedCh used to notify goroutines waiting for the connection to the Signal stream
|
||||||
connWg *sync.WaitGroup //todo use a channel instead??
|
connectedCh chan struct{}
|
||||||
|
mux sync.Mutex
|
||||||
|
// StreamConnected indicates whether this client is StreamConnected to the Signal stream
|
||||||
|
status Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetStatus() Status {
|
||||||
|
return c.status
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close Closes underlying connections to the Signal Exchange
|
// Close Closes underlying connections to the Signal Exchange
|
||||||
@@ -48,7 +62,7 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
|
|||||||
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
sigCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
sigCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
conn, err := grpc.DialContext(
|
conn, err := grpc.DialContext(
|
||||||
sigCtx,
|
sigCtx,
|
||||||
@@ -56,8 +70,8 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
|
|||||||
transportOption,
|
transportOption,
|
||||||
grpc.WithBlock(),
|
grpc.WithBlock(),
|
||||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||||
Time: 3 * time.Second,
|
Time: 15 * time.Second,
|
||||||
Timeout: 2 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -65,60 +79,105 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
return &Client{
|
return &Client{
|
||||||
realClient: proto.NewSignalExchangeClient(conn),
|
realClient: proto.NewSignalExchangeClient(conn),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
signalConn: conn,
|
signalConn: conn,
|
||||||
key: key,
|
key: key,
|
||||||
connWg: &wg,
|
mux: sync.Mutex{},
|
||||||
|
status: StreamDisconnected,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//defaultBackoff is a basic backoff mechanism for general issues
|
//defaultBackoff is a basic backoff mechanism for general issues
|
||||||
func defaultBackoff() backoff.BackOff {
|
func defaultBackoff(ctx context.Context) backoff.BackOff {
|
||||||
return &backoff.ExponentialBackOff{
|
return backoff.WithContext(&backoff.ExponentialBackOff{
|
||||||
InitialInterval: 800 * time.Millisecond,
|
InitialInterval: 800 * time.Millisecond,
|
||||||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||||
Multiplier: backoff.DefaultMultiplier,
|
Multiplier: backoff.DefaultMultiplier,
|
||||||
MaxInterval: 30 * time.Second,
|
MaxInterval: 10 * time.Second,
|
||||||
MaxElapsedTime: 24 * 3 * time.Hour, //stop after 3 days trying
|
MaxElapsedTime: 12 * time.Hour, //stop after 12 hours of trying, the error will be propagated to the general retry of the client
|
||||||
Stop: backoff.Stop,
|
Stop: backoff.Stop,
|
||||||
Clock: backoff.SystemClock,
|
Clock: backoff.SystemClock,
|
||||||
}
|
}, ctx)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive Connects to the Signal Exchange message stream and starts receiving messages.
|
// Receive Connects to the Signal Exchange message stream and starts receiving messages.
|
||||||
// The messages will be handled by msgHandler function provided.
|
// The messages will be handled by msgHandler function provided.
|
||||||
// This function runs a goroutine underneath and reconnects to the Signal Exchange if errors occur (e.g. Exchange restart)
|
// This function is blocking and reconnects to the Signal Exchange if errors occur (e.g. Exchange restart)
|
||||||
// The key is the identifier of our Peer (could be Wireguard public key)
|
// The connection retry logic will try to reconnect for 30 min and if wasn't successful will propagate the error to the function caller.
|
||||||
func (c *Client) Receive(msgHandler func(msg *proto.Message) error) {
|
func (c *Client) Receive(msgHandler func(msg *proto.Message) error) error {
|
||||||
c.connWg.Add(1)
|
|
||||||
go func() {
|
|
||||||
|
|
||||||
var backOff = defaultBackoff()
|
var backOff = defaultBackoff(c.ctx)
|
||||||
|
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
err := c.connect(c.key.PublicKey().String(), msgHandler)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("disconnected from the Signal Exchange due to an error %s. Retrying ... ", err)
|
|
||||||
c.connWg.Add(1)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
backOff.Reset()
|
c.notifyStreamDisconnected()
|
||||||
return nil
|
|
||||||
|
log.Debugf("signal connection state %v", c.signalConn.GetState())
|
||||||
|
if !c.ready() {
|
||||||
|
return fmt.Errorf("no connection to signal")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := backoff.Retry(operation, backOff)
|
// connect to Signal stream identifying ourselves with a public Wireguard key
|
||||||
|
// todo once the key rotation logic has been implemented, consider changing to some other identifier (received from management)
|
||||||
|
stream, err := c.connect(c.key.PublicKey().String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error while communicating with the Signal Exchange %s ", err)
|
log.Warnf("disconnected from the Signal Exchange due to an error: %v", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
c.notifyStreamConnected()
|
||||||
|
|
||||||
|
log.Infof("connected to the Signal Service stream")
|
||||||
|
|
||||||
|
// start receiving messages from the Signal stream (from other peers through signal)
|
||||||
|
err = c.receive(stream, msgHandler)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("disconnected from the Signal Exchange due to an error: %v", err)
|
||||||
|
backOff.Reset()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := backoff.Retry(operation, backOff)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("exiting Signal Service connection retry loop due to unrecoverable error: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (c *Client) notifyStreamDisconnected() {
|
||||||
|
c.mux.Lock()
|
||||||
|
defer c.mux.Unlock()
|
||||||
|
c.status = StreamDisconnected
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) connect(key string, msgHandler func(msg *proto.Message) error) error {
|
func (c *Client) notifyStreamConnected() {
|
||||||
|
c.mux.Lock()
|
||||||
|
defer c.mux.Unlock()
|
||||||
|
c.status = StreamConnected
|
||||||
|
if c.connectedCh != nil {
|
||||||
|
// there are goroutines waiting on this channel -> release them
|
||||||
|
close(c.connectedCh)
|
||||||
|
c.connectedCh = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getStreamStatusChan() <-chan struct{} {
|
||||||
|
c.mux.Lock()
|
||||||
|
defer c.mux.Unlock()
|
||||||
|
if c.connectedCh == nil {
|
||||||
|
c.connectedCh = make(chan struct{})
|
||||||
|
}
|
||||||
|
return c.connectedCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) connect(key string) (proto.SignalExchange_ConnectStreamClient, error) {
|
||||||
c.stream = nil
|
c.stream = nil
|
||||||
|
|
||||||
// add key fingerprint to the request header to be identified on the server side
|
// add key fingerprint to the request header to be identified on the server side
|
||||||
@@ -129,35 +188,48 @@ func (c *Client) connect(key string, msgHandler func(msg *proto.Message) error)
|
|||||||
|
|
||||||
c.stream = stream
|
c.stream = stream
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
// blocks
|
// blocks
|
||||||
header, err := c.stream.Header()
|
header, err := c.stream.Header()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
registered := header.Get(proto.HeaderRegistered)
|
registered := header.Get(proto.HeaderRegistered)
|
||||||
if len(registered) == 0 {
|
if len(registered) == 0 {
|
||||||
return fmt.Errorf("didn't receive a registration header from the Signal server whille connecting to the streams")
|
return nil, fmt.Errorf("didn't receive a registration header from the Signal server whille connecting to the streams")
|
||||||
}
|
}
|
||||||
//connection established we are good to use the stream
|
|
||||||
c.connWg.Done()
|
|
||||||
|
|
||||||
log.Infof("connected to the Signal Exchange Stream")
|
return stream, nil
|
||||||
|
|
||||||
return c.receive(stream, msgHandler)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitConnected waits until the client is connected to the message stream
|
// ready indicates whether the client is okay and ready to be used
|
||||||
func (c *Client) WaitConnected() {
|
// for now it just checks whether gRPC connection to the service is in state Ready
|
||||||
c.connWg.Wait()
|
func (c *Client) ready() bool {
|
||||||
|
return c.signalConn.GetState() == connectivity.Ready
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitStreamConnected waits until the client is connected to the Signal stream
|
||||||
|
func (c *Client) WaitStreamConnected() {
|
||||||
|
|
||||||
|
if c.status == StreamConnected {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := c.getStreamStatusChan()
|
||||||
|
select {
|
||||||
|
case <-c.ctx.Done():
|
||||||
|
case <-ch:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendToStream sends a message to the remote Peer through the Signal Exchange using established stream connection to the Signal Server
|
// SendToStream sends a message to the remote Peer through the Signal Exchange using established stream connection to the Signal Server
|
||||||
// The Client.Receive method must be called before sending messages to establish initial connection to the Signal Exchange
|
// The Client.Receive method must be called before sending messages to establish initial connection to the Signal Exchange
|
||||||
// Client.connWg can be used to wait
|
// Client.connWg can be used to wait
|
||||||
func (c *Client) SendToStream(msg *proto.EncryptedMessage) error {
|
func (c *Client) SendToStream(msg *proto.EncryptedMessage) error {
|
||||||
|
if !c.ready() {
|
||||||
|
return fmt.Errorf("no connection to signal")
|
||||||
|
}
|
||||||
if c.stream == nil {
|
if c.stream == nil {
|
||||||
return fmt.Errorf("connection to the Signal Exchnage has not been established yet. Please call Client.Receive before sending messages")
|
return fmt.Errorf("connection to the Signal Exchnage has not been established yet. Please call Client.Receive before sending messages")
|
||||||
}
|
}
|
||||||
@@ -214,13 +286,17 @@ func (c *Client) encryptMessage(msg *proto.Message) (*proto.EncryptedMessage, er
|
|||||||
// Send sends a message to the remote Peer through the Signal Exchange.
|
// Send sends a message to the remote Peer through the Signal Exchange.
|
||||||
func (c *Client) Send(msg *proto.Message) error {
|
func (c *Client) Send(msg *proto.Message) error {
|
||||||
|
|
||||||
|
if !c.ready() {
|
||||||
|
return fmt.Errorf("no connection to signal")
|
||||||
|
}
|
||||||
|
|
||||||
encryptedMessage, err := c.encryptMessage(msg)
|
encryptedMessage, err := c.encryptMessage(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = c.realClient.Send(context.TODO(), encryptedMessage)
|
_, err = c.realClient.Send(context.TODO(), encryptedMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error while sending message to peer [%s] [error: %v]", msg.RemoteKey, err)
|
//log.Errorf("error while sending message to peer [%s] [error: %v]", msg.RemoteKey, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,10 +313,10 @@ func (c *Client) receive(stream proto.SignalExchange_ConnectStreamClient,
|
|||||||
log.Warnf("stream canceled (usually indicates shutdown)")
|
log.Warnf("stream canceled (usually indicates shutdown)")
|
||||||
return err
|
return err
|
||||||
} else if s.Code() == codes.Unavailable {
|
} else if s.Code() == codes.Unavailable {
|
||||||
log.Warnf("server has been stopped")
|
log.Warnf("Signal Service is unavailable")
|
||||||
return err
|
return err
|
||||||
} else if err == io.EOF {
|
} else if err == io.EOF {
|
||||||
log.Warnf("stream closed by server")
|
log.Warnf("Signal Service stream closed by server")
|
||||||
return err
|
return err
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -48,30 +48,42 @@ var _ = Describe("Client", func() {
|
|||||||
// connect PeerA to Signal
|
// connect PeerA to Signal
|
||||||
keyA, _ := wgtypes.GenerateKey()
|
keyA, _ := wgtypes.GenerateKey()
|
||||||
clientA := createSignalClient(addr, keyA)
|
clientA := createSignalClient(addr, keyA)
|
||||||
clientA.Receive(func(msg *sigProto.Message) error {
|
go func() {
|
||||||
receivedOnA = msg.GetBody().GetPayload()
|
err := clientA.Receive(func(msg *sigProto.Message) error {
|
||||||
msgReceived.Done()
|
receivedOnA = msg.GetBody().GetPayload()
|
||||||
return nil
|
msgReceived.Done()
|
||||||
})
|
return nil
|
||||||
clientA.WaitConnected()
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
clientA.WaitStreamConnected()
|
||||||
|
|
||||||
// connect PeerB to Signal
|
// connect PeerB to Signal
|
||||||
keyB, _ := wgtypes.GenerateKey()
|
keyB, _ := wgtypes.GenerateKey()
|
||||||
clientB := createSignalClient(addr, keyB)
|
clientB := createSignalClient(addr, keyB)
|
||||||
clientB.Receive(func(msg *sigProto.Message) error {
|
|
||||||
receivedOnB = msg.GetBody().GetPayload()
|
go func() {
|
||||||
err := clientB.Send(&sigProto.Message{
|
err := clientB.Receive(func(msg *sigProto.Message) error {
|
||||||
Key: keyB.PublicKey().String(),
|
receivedOnB = msg.GetBody().GetPayload()
|
||||||
RemoteKey: keyA.PublicKey().String(),
|
err := clientB.Send(&sigProto.Message{
|
||||||
Body: &sigProto.Body{Payload: "pong"},
|
Key: keyB.PublicKey().String(),
|
||||||
|
RemoteKey: keyA.PublicKey().String(),
|
||||||
|
Body: &sigProto.Body{Payload: "pong"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
Fail("failed sending a message to PeerA")
|
||||||
|
}
|
||||||
|
msgReceived.Done()
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fail("failed sending a message to PeerA")
|
return
|
||||||
}
|
}
|
||||||
msgReceived.Done()
|
}()
|
||||||
return nil
|
|
||||||
})
|
clientB.WaitStreamConnected()
|
||||||
clientB.WaitConnected()
|
|
||||||
|
|
||||||
// PeerA initiates ping-pong
|
// PeerA initiates ping-pong
|
||||||
err := clientA.Send(&sigProto.Message{
|
err := clientA.Send(&sigProto.Message{
|
||||||
@@ -100,11 +112,15 @@ var _ = Describe("Client", func() {
|
|||||||
|
|
||||||
key, _ := wgtypes.GenerateKey()
|
key, _ := wgtypes.GenerateKey()
|
||||||
client := createSignalClient(addr, key)
|
client := createSignalClient(addr, key)
|
||||||
client.Receive(func(msg *sigProto.Message) error {
|
go func() {
|
||||||
return nil
|
err := client.Receive(func(msg *sigProto.Message) error {
|
||||||
})
|
return nil
|
||||||
client.WaitConnected()
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
client.WaitStreamConnected()
|
||||||
Expect(client).NotTo(BeNil())
|
Expect(client).NotTo(BeNil())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||